当前位置:科技动态 > 【语音识别】基于Python构建简单的录音和语音识别应用

【语音识别】基于Python构建简单的录音和语音识别应用

  • 发布:2023-10-02 02:29

语音识别技术的快速发展为实现更多智能应用提供了无限可能。本文旨在介绍一个基于Python的简单录音和语音识别应用程序。文章简单介绍了相关技术的应用,重点是录音,而语音识别则重点是调用相关语音识别库。本文将首先概述一些基本的音频概念,然后详细讲解如何使用PyAudio库和SpeechRecognition库来实现录音功能。最后,构建一个简单的语音识别示例应用程序,可以实时监听音频的开始和结束,将录制的音频数据传输到Whisper语音识别库中进行语音识别,最后将识别结果输出到一个简单的程序中PyQt5 页面。

本文所有代码请参见:Python学习笔记

内容
  • 0 基本音频概念
  • 1 PyAudio
    • 1.1 PyAudio介绍与安装
    • 1.2 音频录制与播放
      • 1.2.1 音频播放
      • 1.2.2 音频录制
      • 1.2.3 全双工录音和播放
    • 1.3 回调函数的使用
    • 1.4 设备管理
  • 2 语音识别
    • 2.1 语音识别介绍与安装
    • 2.2 示例代码
  • 3 语音识别应用示例
  • 4 参考

0 基本音频概念

随着深度学习技术的快速发展,端到端语音识别得到了广泛的应用。不过,我们还是需要对音频相关的最基本的概念有一定的了解,比如采样频率、采样位数等。声音是由物体振动引起的机械波,而音频是声音的电子表示。 PCM(脉冲编码调制)编码是将模拟音频信号转换为数字形式的常用方法。在这个过程中,音频采样是指在一段时间内以固定间隔采集声音的幅度值,将连续的声音模拟信号转换为离散的数字数据。采样频率代表每秒采集的样本数,而采样位数则代表每个样本的量化级别,也就是声音的精细度和动态范围。有关音频概念的详细介绍,请参阅:从 PCM 开始的数字音频基础知识。

采样频率

音频信号通常是连续的模拟波形,为了存储它们需要离散化。这是通过采样来实现的,采样以固定的时间间隔测量声音信号的幅度。采样的过程就是提取模拟信号各点的频率值。采样率越高,即1秒内提取的数据点越多,音频质量越好,但也增加了存储和处理成本。奈奎斯特-香农采样定理强调采样频率必须高于信号最大频率的两倍,才能确保从采样值中完全恢复原始模拟信号。在音频信号采样领域,经常使用两种主要的采样频率:16kHz和44.1kHz。
例如,16kHz表示每秒采样16,000次,人类语音的频率范围为200Hz至8kHz。 16kHz的采样频率足以捕捉人类语音的频率特征,同时减轻音频数据存储和处理的负担。因此,常用的语音采样频率为16kHz。人耳可以感知20Hz至20kHz的声音。为了呈现高质量的音频,通常选择44.1kHz采样频率,以覆盖人耳可听声音的上限。

采样位和通道

采样信号是连续的模拟值。为了将其转换为数字形式,需要对信号进行量化。量化是将连续的模拟值映射到离散的数字值,通常使用固定数量的采样位(例如16或24位)来表示样本的幅度范围。例如,用16位(16bit),即两个字节的二进制信号来表示音频样本,16位的取值范围为-32768到32767,总共有65536个可能的值。因此,最终模拟的音频信号在幅度上被分为65536个数值级别。采样位数越高,可以表示更大范围的声音幅度,并保留更详细的信息。常用的位深度包括8位、16位和24位,其中8位是最低要求,16位可以满足一般应用的需求,24位适合专业音频工作。

通道是指播放系统或录音系统中音频信号的传输通道。通道通常对应于单个音频源或信号流,并负责将该信号传输到扬声器或录音设备。在立体声系统中,通常有两个声道,即左声道和右声道,分别用于处理来自音源的左、右声音信号,以达到空间立体声效果。音频通道的概念还可以扩展到多通道系统,例如5.1通道、7.1通道等,可以支持更多的音源和更丰富的音效体验,例如环绕声效果。

常用音频编码格式

PCM编码得到的音频数据是最原始的,需要经过两次编码才能存储和网络传输。这些二次音频编码格式在PCM编码的基础上进行重新编码和压缩,根据压缩方式分为无损压缩和有损压缩。无损压缩意味着与 PCM 编码相比,音频数据的音质得以完全保留。但是,无损压缩音频文件通常比有损压缩音频文件稍大。在有损压缩的编码过程中,为了减小文件大小,会牺牲音频数据的部分信息和音质。

无损压缩常见的音频编码格式包括:WAV/WAEV(波形音频文件格式)、FLAC(免费无损音频编解码器)、AIFF(音频交换文件格式)等。有损压缩常见的音频编码格式包括:MP3 (MPEG 音频层 III)、AAC(高级音频编码)、WMA(Windows Media 音频)等

获得编码后的音频数据后,需要使用合适的文件格式来保存编码后的数据。一种音频编码可以对应一种文件格式,也可以对应多种文件格式,通常是一种。例如,WAV编码数据对应于.wav文件格式,MP3编码数据对应于.mp3文件格式。 PCM编码数据对应.raw或.pcm文件格式,AAC编码数据对应.acc或.mp4文件格式等。

音视频概念对比

概念 音频 视频
尺寸 通过声波传递的声音信息是一维的 通过图像序列传输的运动图像信息是二维的
核心功能 包括音高、音量、节奏等,用频率和幅度来表示 包括屏幕内容、颜色等,用像素和颜色来表达
信号频率 用采样率来表达,人类对声音比较敏感,所以音频采样率远大于视频帧率 以帧率表示,视频通过多张静止图像以一定的速度播放,模拟流畅的动画
采样精度 用于表示声音的幅度值,常见为16bit 用于表示图像的颜色和亮度值,通常为8bit(256种颜色)
加工技术 均衡、压缩、降噪等 编辑、特效、编解码等
频道 以通道表示,例如单通道和双通道 用颜色通道表示,如GRAY、RGB、RGBA
存储 易于转移和存放,占用空间较小 需要更大的存储空间和带宽来传输和保存

以下代码显示读取音频内容为123456789的wav文件并绘制音频数据的波形图。

# 导入matplotlib库进行绘图
从 matplotlib 导入 pyplot 作为 plt
#导入soundfile库用于读取音频文件
# pip 安装声音文件
将声音文件导入为 sf
# 从demo.wav文件中读取音频数据和采样率,data是一个numpy数组
数据,采样率 = www.sychzs.cn('asr_example_hotword.wav', dtype='float32')
# 保存音频
sf.write(“输出.wav”,数据=数据,采样率=采样率)
#打印音频数据的形状并打印采样率
# data 是一个 numpy 数组,samplerate 是一个整数
print('数据形状:{}'.format(data.shape))
print('采样率: {}'.format(samplerate))
# 绘制音频波形
plt.figure()
plt.plot(数据)
www.sychzs.cn()

代码运行结果如下,代表音频数据的样本点索引,即音频的时间线。每个采样点对应每一帧音频数据,从左到右递增。纵轴表示每个时间点的音频信号的归一化音频强度。读取的数据以float32格式表示,数值采样值范围为-32678~32678。声音文件库会将其除以 32678 (2^16/2) 以将其标准化为 [-1, 1] 区间。

1 PyAudio

1.1 PyAudio介绍及安装

PyAudio 是一个用于处理音频输入和输出的 Python 库。其主要变量和接口的实现依赖于PortAudio的C语言版本。 PyAudio 提供了从麦克风或其他输入设备录制音频、保存音频文件、实时处理音频数据以及播放音频文件或实时音频流等功能。此外,PyAudio还允许您设置采样率、位深度和通道数等参数,并支持回调函数和事件驱动机制,以满足不同的应用需求。 PyAudio 官方网站请参见:PyAudio。 PyAudio的安装需要Python 3.7及以上版本。

Windows下PyAudio安装命令如下:

python -m pip install pyaudio

Linux下PyAudio的命令如下:

sudo apt-get install python3-pyaudio
python -m pip 安装 pyaudio

本文使用的PyAudio版本是0.2.13。

1.2 音频录制和播放

1.2.1 音频播放

以下代码展示了基于PyAudio播放本地音频文件。

#wave是python处理音频的标准库
导入波
导入pyaudio
# 定义每次从音频文件中读取的音频样本数据点的数量
块 = 1024
文件路径=“演示.wav”
# 以音频二进制流的形式打开音频文件
将 www.sychzs.cn(filepath, 'rb') 作为 wf:
# 实例化PyAudio并初始化PortAudio系统资源
p = pyaudio.PyAudio()
# 打开音频流
# format:指定音频流的采样格式。其中,wf.getsampwidth()用于获取音频文件的采样位数(样本宽度)。
# 采样位数是指每个采样点占用的字节数。通常采样位数可以是1字节(8位)、2字节(16位)等。
#channels:指定音频流的通道数。通道数可以是单声道 (1) 或立体声 (2)
#rate:指定音频流的采样率。采样率表示每秒音频样本的数量。常见的采样率为 44100Hz 或 16000Hz。
# 输出:是否播放音频
流 = www.sychzs.cn(format=p.get_format_from_width(wf.getsampwidth()),
频道=wf.getnchannels(),
速率=wf.getframerate(),
输出=真)
# 播放音频文件中的样本数据
而真实:
#data是二进制数据
数据 = wf.readframes(CHUNK)
#len(data)表示读取数据的长度
# 这里len(data)应该等于采样点占用的字节数wf.getsampwidth()乘以CHUNKiflen(数据):
流.write(数据)
别的:
休息
# 或者使用python3.8中引入的walrus运算符
# while len(data := wf.readframes(CHUNK)):
# 流.write(数据)
# 关闭音频流
流.close()
# 释放PortAudio系统资源
p.terminate()

1.2.2 录音

以下代码显示基于PyAudio调用麦克风录音,并将录音结果保存为本地文件。

进口波
导入pyaudio
#设置音频流的数据块大小
块 = 1024
# 设置音频流的格式为16位整数,即2个字节
格式 = pyaudio.paInt16
#设置音频流的通道数为1
频道 = 1
# 设置音频流的采样率为16KHz
利率 = 16000
#设置录音时长为5秒
记录_秒 = 5
输出文件路径 = '输出.wav'
将 www.sychzs.cn(outfilepath, 'wb') 用作 wf:
p = pyaudio.PyAudio()
#设置波形文件的通道数
wf.setnchannels(频道)
#设置wave文件的采样位数
wf.setsampwidth(p.get_sample_size(FORMAT))
#设置wave文件的采样率
wf.setframerate(速率)
# 打开音频流,输入表示录音
流= www.sychzs.cn(格式=格式,频道=频道,速率=速率,输入=真)
print('正在录音...')
# 循环写入音频数据
for _ in range(0, RATE // CHUNK * RECORD_SECONDS):
wf.writeframes(www.sychzs.cn(CHUNK))
打印('完成')
流.close()
p.terminate()

1.2.3 全双工录音和播放

全双工系统(full-duplex)可以同时进行两路数据传输,而半双工系统(half-duplex)只能同时进行单路数据传输。在半双工系统中,当一个设备传输数据时,另一设备必须等待传输完成才能处理数据。下面的代码演示了全双工音频录制和播放,即同时录制和播放音频,无需等待一项操作完成后再执行另一项操作。

导入pyaudio
记录_秒 = 5
块 = 1024
利率 = 16000
p = pyaudio.PyAudio()
#frames_per_buffer 设置每个音频缓冲区的大小
流 = www.sychzs.cn(format=p.get_format_from_width(2),
通道=1,
费率=费率,
输入=真,
输出=真,
每个缓冲区的帧数=块)
打印('录音')
对于范围内的 i(0, int(RATE / CHUNK * RECORD_SECONDS)):
# read 读取音频,然后 writer 播放音频
流.write(流.读(CHUNK))
打印('完成')
流.close()
p.terminate()

1.3 回调函数的使用

在前面的代码中,PyAudio通过阻塞主线程来执行音频播放或录音,这意味着代码无法同时处理其他任务。为了解决这个问题,PyAudio提供了回调函数,使得程序在进行音频输入输出时能够以非阻塞的方式进行操作,即在处理音频流的同时处理其他任务。 PyAudio 回调函数在单独的线程中执行。当音频流数据可用时,自动调用回调函数并立即处理音频数据。 PyAudio回调函数有固定的参数接口。功能介绍如下:

def callback(in_data, # 录制的音频数据的字节流,如果没有录制则为 Noneframe_count, #每个缓冲区的帧数,本次读取的数据量
time_info, # 音频流时间信息字典
status_flags) #音频流状态标志位

以下代码展示了如何以回调函数的形式播放音频。

进口波
导入时间
导入pyaudio
文件路径=“演示.wav”
将 www.sychzs.cn(filepath, 'rb') 作为 wf:
# 当音频流数据可用时,会自动调用回调函数
def回调(in_data,frame_count,time_info,状态):
# 读取指定数量的音频帧数据
数据 = wf.readframes(frame_count)
#pyaudio.paContinue 是一个常量,表示音频流处理将继续。
# 根据需要更改为pyaudio.paAbort或pyaudio.paComplete等常量,以控制处理过程的中断和结束
返回(数据,pyaudio.paContinue)
p = pyaudio.PyAudio()
#stream_callback设置回调函数
流 = www.sychzs.cn(format=p.get_format_from_width(wf.getsampwidth()),
频道=wf.getnchannels(),
速率=wf.getframerate(),
输出=真,
Stream_callback=回调)
# 判断音频流是否处于活动状态
而www.sychzs.cn_active():
睡眠时间(0.1)
流.close()
p.terminate()

以下代码展示了如何使用回调函数实现全双工模式的录音和播放。如果超时,通过调用stream.close()关闭音频流并释放相关资源。一旦音频流关闭,就不能再传输音频数据。如果想在录音过程中暂停一段时间再继续录音,可以使用stream.stop_stream()暂停音频流的数据传输,即暂时停止音频的读写,但是仍然保持流对象打开。随后,可以通过调用stream.start_stream()来重新启动音频流的数据传输。

导入时间
导入pyaudio
# 录音时长
持续时间 = 5
def回调(in_data,frame_count,time_info,状态):
# in_data为麦克风输入的音频流
返回(in_data,pyaudio.paContinue)
p = pyaudio.PyAudio()
流 = www.sychzs.cn(format=p.get_format_from_width(2),
通道=1,
率=16000,
输入=真,
输出=真,
Stream_callback=回调)
开始 = 时间.time()
# 当音频流处于活动状态且录音时间未达到设定时长时
www.sychzs.cn_active() 和 (time.time() - start) < DURATION:
睡眠时间(0.1)
# 超过持续时间后关闭音频流
流.close()
p.terminate()

1.4 设备管理

PyAudio提供了host Api和device Api来获取音频设备,但是host Api和device Api代表的是不同的层次和功能。详情如下:

  • host Api:是底层音频系统的抽象,代表系统上可用的音频接口,提供与底层音频设备交互的功能。每个主机 API 都有自己的特点和支持的功能集,例如使用的数据格式、采样率等。常见的主机 API 包括 ALSA、PulseAudio、CoreAudio 等。
  • device Api:指特定的音频输入或输出设备,例如麦克风、扬声器或耳机等。每个音频设备都属于特定的音频主机API,并且具有不同的参数配置,例如采样率、缓冲区大小等等

本文主要介绍比较常用的设备Api。 PyAudio中设备Api的功能如下:

  1. 通过索引获取设备信息(索引)
    通过整数索引获取指定设备的详细信息。该函数返回一个包含设备信息的字典,包括设备名称、输入/输出通道数、支持的采样率范围等。

  2. get_default_input_device_info()
    获取默认输入设备的详细信息。该函数返回一个包含设备信息的字典。

  3. get_default_output_device_info()
    获取默认输出设备的详细信息。该函数返回一个包含设备信息的字典。

  4. get_device_count()
    获取计算机上可用的音频设备数量,可以是麦克风、扬声器、音频接口等。

默认设备是当前操作系统的默认音频设备。可以通过操作系统音频控制页面更改默认的音频输入和输出设备。以下代码展示了这些函数的使用:

导入pyaudio
# 获取指定设备的详细信息
def get_device_info_by_index(索引):
p = pyaudio.PyAudio()
device_info = p.get_device_info_by_index(索引)
p.terminate()
返回设备信息
# 获取默认输入设备的详细信息
def get_default_input_device_info():
p = pyaudio.PyAudio()
default_input_info = p.get_default_input_device_info()p.terminate()
返回默认输入信息
# 获取默认输出设备的详细信息
def get_default_output_device_info():
p = pyaudio.PyAudio()
default_output_info = p.get_default_output_device_info()
p.terminate()
返回默认输出信息
# 获取计算机上可用的音频设备数量
def get_device_count():
p = pyaudio.PyAudio()
device_count = p.get_device_count()
p.terminate()
返回设备计数
# 用法示例
索引 = 0
print("可用音频设备数量:", get_device_count())
print("设备{}信息:{}".format(index, get_device_info_by_index(index)))
print("默认录音设备信息:", get_default_input_device_info())
print("默认播放设备信息:", get_default_output_device_info())

上述代码,返回的默认播放设备信息字典如下:

默认播放设备信息:
{'索引':3,
'结构版本': 2,
'name': '扬声器(Realtek High Definition Au',
'主机API':0,
'最大输入通道': 0,
'最大输出通道': 2,
“默认低输入延迟”:0.09,
“默认低输出延迟”:0.09,
“默认高输入延迟”:0.18,
“默认高输出延迟”:0.18,
“默认采样率”:44100.0}

该设备也是系统当前默认的音频设备。各参数含义如下:

  • 'index': 3:设备索引号,用于在设备列表中唯一标识该设备。
  • 'structVersion': 2:设备信息结构的版本号,用于指示设备信息的数据结构版本。
  • 'name':'Speaker (Realtek High Definition Au':设备的名称,表明该设备是Realtek High Definition型号扬声器。
  • 'hostApi': 0:设备声卡驱动模式,来自 PortAudio。如果想了解更多,请参见:pyaudio声卡信息中的hostApi。
  • 'maxInputChannels':0:设备支持的最大输入通道数。这里,0表示设备没有输入功能,不支持录音。
  • 'maxOutputChannels': 2:设备支持的最大输出通道数。这里的2表示设备支持2个输出通道,即可以播放立体声音频。
  • 'defaultLowInputLatency':0.09:默认低输入延迟,以秒为单位,表示音频输入信号进入设备所需的最短时间。
  • 'defaultLowOutputLatency':0.09:默认低输出延迟,以秒为单位,表示设备的输出信号到达音频输出所需的最短时间。
  • 'defaultHighInputLatency': 0.18:默认高输入延迟,以秒为单位,表示音频输入信号进入设备所需的最长时间。
  • 'defaultHighOutputLatency':0.18:默认高输出延迟,以秒为单位,表示设备的输出信号到达音频输出所需的最长时间。
  • 'defaultSampleRate': 44100.0:默认采样率,表示设备支持的默认音频采样率为44100赫兹(Hz)。这是音频设备每单位时间采集的样本数,影响声音的质量和频率范围。

如果要指定录音或者录音的设备,在open函数中指定设备的索引,代码如下:

导入pyaudio
p = pyaudio.PyAudio()
# 获取可用设备数量
device_count = p.get_device_count()
# 遍历设备并打印设备信息和索引
对于范围内的 i(device_count):
device_info = p.get_device_info_by_index(i)
print(f"设备 {i}: {device_info['name']}")
# 选择所需录音设备的索引
输入设备索引 = 1
# 选择所需播放设备的索引
输出设备索引 = 2
# 打开音频流并指定设备
流 = www.sychzs.cn(format=p.get_format_from_width(2),
通道=1,
率=16000,
输入=真,
输出=真,
输入设备索引=输入设备索引,
输出设备索引 = 输出设备索引)
# 操作输出设备和记录设备
#...

2 语音识别

2.1 语音识别介绍及安装

SpeechRecognition 是一个用于语音识别的 Python 库,支持多种语音识别引擎将音频转换为文本。 SpeechRecognition开源仓库的地址是:speech_recognition。 SpeechRecognition基于PyAudio库,封装了更加全面、全面的录音功能。本文主要介绍使用SpeechRecognition来录制音频。使用 SpeechRecognition 进行录音需要 Python 3.8 及更高版本,以及最低 PyAudio 版本 0.2.11。安装PyAudio后,SpeechRecognition安装命令如下:

pip 安装语音识别

语音识别的主要类有:

音频数据

AudioData 类用于表示语音数据。主要参数及功能如下:

参数

  • frame_data:音频字节流数据
  • sample_rate:音频采样率
  • sample_width:音频采样位数

功能

  • get_segment:返回指定时间段内音频数据的AudioData对象
  • get_raw_data:返回音频数据的原始字节流
  • get_wav_data:返回音频数据的wav格式字节流
  • get_aiff_data:返回音频数据的aiff格式字节流
  • get_flac_data:返回音频数据的flac格式字节流

麦克风

Microphone类是封装PyAudio的类,用于驱动麦克风设备功能。因此,PyAudio的构建参数以及主要参数和功能如下:

参数

  • device_index:麦克风设备的索引号。如果未指定,将使用 PyAudio 的默认音频输入设置
  • format:采样格式为16位整数。如果未指定,将使用 PyAudio 的默认音频输入设置
  • SAMPLE_WIDTH:音频采样位数。如果未指定,将使用 PyAudio 的默认音频输入设置
  • SAMPLE_RATE:采样率,如果不指定,将使用PyAudio的默认音频输入设置
  • CHUNK:每个缓冲区中存储的帧数,默认为1024
  • audio: PyAudio对象
  • stream:调用PyAudio的open函数打开的音频流

函数

  • get_pyaudio:用来获取PyAudio的版本号,并调用PyAudio库
  • list_microphone_names:返回当前系统中所有可用的麦克风设备的名称列表
  • list_working_microphones:返回当前系统中所有正在工作的麦克风设备的名称列表。麦克风设备是否运行的评定方式为:对于某设备,尝试录制一段短暂的音频,然后检查是否成功录制到了具有一定音频能量的音频数据。

Recognizer类

Recognizer类是用于语音识别的主要类,它提供了一系列参数和函数来处理音频输入,主要参数和函数如下:

参数

  • energy_threshold = 300: 用于录制最低音频能量,基于音频均方根RMS计算能量
  • dynamic_energy_threshold = True: 是否使用动态能量阈值
  • dynamic_energy_adjustment_damping = 0.15: 能量阈值调整的阻尼系数
  • dynamic_energy_ratio = 1.5: 动态能量比率
  • pause_threshold = 0.8: 在一段完整短语被认为结束之前,非语音音频的持续时间(以秒为单位)
  • operation_timeout = None: 内部操作(例如API请求)开始后超时的时间(以秒为单位),如果不设置超时时间,则为None
  • phrase_threshold = 0.3: 认为一段语音至少需要的持续时间(以秒为单位),低于该值的语音将被忽略(用于过滤噪声)
  • non_speaking_duration = 0.5: 非语音音频的持续时间(以秒为单位)

函数

  • record:从一个音频源中读取数据
  • adjust_for_ambient_noise:用于在录制音频之前自动根据麦克风的环境噪声水平调整energy_threshold参数
  • listen:音频录制,结果返回AudioData类
  • listen_in_background:用于在后台录制音频并调用回调函数

Recognizer类的listen函数每次录音分为三个阶段:

  1. 录音起始
    这一阶段意味着开始录音但是没有声音输入。如果当前获得的声音片段能量值低于energy_threshold,则认为没有声音输入。一旦当前获得的声音片段能量值高于energy_threshold,则进入下一阶段。该阶段将最多保存non_speaking_duration长度的音频片段。如果dynamic_energy_threshold为True,则会根据环境动态调整energy_threshold
    listen函数提供输入参数timeout以控制该阶段时长,如果录音处于该阶段timeout秒则停止录音返回错误提示,timeout默认为None。

  2. 录音中
    这一阶段意味着已有声音输入。如果声音片段能量值低于energy_threshold连续超过pause_threshold秒,则结束录音。在这一阶段energy_threshold一直是固定值,并不会进行动态调整。
    listen函数提供输入参数phrase_time_limit以控制该阶段最大时长,如果录音处于该阶段phrase_time_limit秒则结束录音。

  3. 录音结束
    在这一阶段中,如果录音中阶段获得的声音片段时间不超过phrase_threshold秒,则不返回录音结果且进入下一次录音起始阶段。如果超过phrase_threshold秒,则将音频片段转为音频流,以AudioData对象返回。

2.2 示例代码

音频录制

import speech_recognition as sr
# 创建一个Recognizer对象,用于语音识别
r = sr.Recognizer()
# 设置相关阈值
r.non_speaking_duration = 0.3
r.pause_threshold = 0.5
# 创建一个Microphone对象,设置采样率为16000
# 构造函数所需参数device_index=None, sample_rate=None, chunk_size=1024
msr = sr.Microphone(sample_rate=16000)
# 打开麦克风
with msr as source:
# 如果想连续录音,该段代码使用for循环
# 进行环境噪音适应,duration为适应时间,不能小于0.5
# 如果无噪声适应要求,该段代码可以注释
r.adjust_for_ambient_noise(source, duration=0.5)
print("开始录音")
# 使用Recognizer监听麦克风录音
# phrase_time_limit=None表示不设置时间限制
audio = r.listen(source, phrase_time_limit=None)
print("录音结束")
# 将录音数据写入.wav格式文件
with open("microphone-results.wav", "wb") as f:
# audio.get_wav_data()获得wav格式的音频二进制数据
f.write(audio.get_wav_data())
# 将录音数据写入.raw格式文件
with open("microphone-results.raw", "wb") as f:
f.write(audio.get_raw_data())
# 将录音数据写入.aiff格式文件
with open("microphone-results.aiff", "wb") as f:
f.write(audio.get_aiff_data())
# 将录音数据写入.flac格式文件
with open("microphone-results.flac", "wb") as f:
f.write(audio.get_flac_data())

音频文件读取

# 导入speech_recognition库,别名为sr
import speech_recognition as sr
# 创建一个Recognizer对象r,用于语音识别
r = sr.Recognizer()
# 设置音频文件路径
filepath = "demo.wav"
# 使用AudioFile打开音频文件作为音频源
with sr.AudioFile(filepath) as source:
# 使用record方法记录从音频源中提取的2秒音频,从第1秒开始
audio = r.record(source, offset=1, duration=2)
# 创建一个文件用于保存提取的音频数据
with open("microphone-results.wav", "wb") as f:
# 将提取的音频数据写入文件
f.write(audio.get_wav_data())

回调函数的使用

import time
import speech_recognition as sr
# 这是从后台线程调用的回调函数
def callback(recognizer, audio):
# recognizer是Recognizer对象的实例。audio是从麦克风捕获到的音频数据
print(type(audio))
r = sr.Recognizer()
m = sr.Microphone()
with m as source:
# 我们只需要在开始监听之前校准一次
r.adjust_for_ambient_noise(source)
# 在后台开始监听
stop_listening = r.listen_in_background(m, callback)
# 进行一些无关的计算,持续5秒钟
for _ in range(50):
# 即使主线程正在做其他事情,我们仍然在监听
time.sleep(0.1)
# 调用此函数请求停止后台监听
stop_listening(wait_for_stop=False)

麦克风设备查看

import speech_recognition as sr
# 获取麦克风设备名称列表
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")
# 获取可用的工作麦克风列表
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对象
def get_pyaudio():
audio = sr.Microphone.get_pyaudio().PyAudio()
# 获取默认音频输入设备信息
print(audio.get_default_input_device_info())
print("\n")
return audio
print("所有麦克风列表")
list_microphone_names()
print("可运行麦克风列表")
list_working_microphones()
print("默认音频输入设备信息")
get_pyaudio()

3 语音识别示例应用

本示例给出一个基于SpeechRecognition库和Whisper语音识别库的非流式语音识别示例应用。一般来说语音识别分为流式语音识别和非流式语音识别:

  • 流式语音识别是指在语音输入过程中实时进行语音识别,即边接收语音数据边输出识别结果,实现实时性较高的语音识别。在流式语音识别中,语音被分割成一小段一小段的流,可以通过连续发送这些流来实时地获取识别结果。随着语音输入的增加,流式语音识别也可以优化输出部分结果。流式语音识别准确率相对较低,但是实时性强,适用于需要快速响应的场景,例如实时语音助手、电话客服、会议记录等。技术上,流式语音识别需要实时处理音频流,要求算法具有低延迟和高吞吐量的特点,通常使用各种优化策略来提高实时性能。
  • 非流式语音识别是指等待语音输入结束后将完整的语音输入一次性进行分析和识别。非流式语音识别精度高,适用于一些不需要实时响应的场景或一次性识别整段语音的场景,如指令识别、语音转写、语音搜索、语音翻译等。技术上,非流式语音识别注重语音的整体准确性和语义理解,通常采用复杂的模型和算法来提高识别准确率。

Whisper是OpenAI开源的通用多语言语音识别模型库。Whisper使用了一个序列到序列的Transformer模型,支持多国语言语音识别,其英语的识别水平与人类接近。关于Whisper的安装和使用可参考Whisper开源仓库或参考文章:Whisper语音转文字手把手教程。所提供的语音识别示例实现了简单的语音起始和结束检测,并进行相应的语音识别和结果展示,程序代码结构如下:

.
├── www.sychzs.cn 语音识别类
├── www.sychzs.cn 录音类
└── www.sychzs.cn 界面类

安装SpeechRecognition库和Whisper库后运行www.sychzs.cn文件即可打开示例应用。

界面类

界面类提供了一个基于PyQt5编写的简单应用界面,如下所示。当界面初始化时,会同时初始化录音类和语音识别类。点击开始录音按钮后,程序将实现自动循环监听说话音频的开始和结束。每次说话结束后,程序会自动进行语音识别,并将识别结果显示在界面中。点击停止按钮则会等待录音结束并停止语音监听。

# www.sychzs.cn
from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize, Qt
import sys
from record import AudioHandle
class Window(QMainWindow):
"""
界面类
"""
def __init__(self):
super().__init__()
# --- 设置标题
self.setWindowTitle('语音识别demo')
# --- 设置窗口尺寸
# 获取系统桌面尺寸
desktop = app.desktop()
# 设置界面初始尺寸
self.width = int(desktop.screenGeometry().width() * 0.3)
self.height = int(0.5 * self.width)
self.resize(self.width, self.height)
# 设置窗口最小值
self.minWidth = 300
self.setMinimumSize(QSize(self.minWidth, int(0.5 * self.minWidth)))
# --- 创建组件
self.showBox = QTextEdit()
self.showBox.setReadOnly(True)
self.startBtn = QPushButton("开始录音")
self.stopBtn = QPushButton("停止录音")
self.stopBtn.setEnabled(False)
# --- 组件初始化
self.initUI()
# --- 初始化音频类
self.ahl = AudioHandle()
# 连接用于传递信息的信号
self.ahl.infoSignal.connect(self.showInfo)
self.showInfo("{}".format("程序已初始化"))
def initUI(self) -> None:
"""
界面初始化
"""
# 设置整体布局
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.showBox)
# 设置底部水平布局
blayout = QHBoxLayout()
blayout.addWidget(self.startBtn)
blayout.addWidget(self.stopBtn)
mainLayout.addLayout(blayout)
mainWidget = QWidget()
mainWidget.setLayout(mainLayout)
self.setCentralWidget(mainWidget)
# 设置事件
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("请等待")
waitDialog.setWindowModality(Qt.ApplicationModal)
waitDialog.setCancelButton(None)
waitDialog.setRange(0, 0)
# 设置 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)
# 关闭对话框边框
waitDialog.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
# 连接关闭信号,即ahl线程结束则waitDialog关闭
self.ahl.finished.connect(waitDialog.accept)
# 结束录音线程
self.ahl.stop()
if self.ahl.isRunning():
# 显示对话框
waitDialog.exec_()
# 关闭对话框
self.ahl.finished.disconnect(waitDialog.accept)
waitDialog.close()
self.startBtn.setEnabled(True)
def showInfo(self, text: str) -> None:
"""
信息展示函数
:param text: 输入文字,可支持html
"""
self.showBox.append(text)
if not self.ahl.running:
www.sychzs.cn()
def closeEvent(self, event: QtGui.QCloseEvent):
"""
重写退出事件
:param event: 事件对象
"""
# 点击停止按钮
if self.ahl.running:
www.sychzs.cn()
del self.ahl
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Window()
# 获取默认图标
default_icon = www.sychzs.cn().standardIcon(QStyle.SP_MediaVolume)
# 设置窗口图标为默认图标
ex.setWindowIcon(default_icon)
www.sychzs.cn()
sys.exit(app.exec_())

录音类

录音类可以用于监听麦克风输入的音频并调用语音识别类进行识别。通过设置采样率、适应环境时长、录音最长时长等参数,实现自动判断说话开始和结束的功能。同时,通过PyQt5的信号机制,在界面上展示不同类型的信息,包括警告信息和识别结果。

# www.sychzs.cn
import speech_recognition as sr
from PyQt5.QtCore import QThread, pyqtSignal
import time, os
import numpy as np
from asr import ASR
class AudioHandle(QThread):
"""
录音控制类
"""
# 用于展示信息的pyqt信号
infoSignal = pyqtSignal(str)
def __init__(self, sampleRate: int = 16000, adjustTime: int = 1, phraseLimitTime: int = 5,
saveAudio: bool = False, hotWord: str = ""):
"""
:param sampleRate: 采样率
:param adjustTime: 适应环境时长/s
:param phraseLimitTime: 录音最长时长/s
:param saveAudio: 是否保存音频
:param hotWord: 热词数据
"""
super(AudioHandle, self).__init__()
self.sampleRate = sampleRate
self.duration = adjustTime
self.phraseTime = phraseLimitTime
# 用于设置运行状态
self.running = False
self.rec = sr.Recognizer()
# 麦克风对象
self.mic = sr.Microphone(sample_rate=self.sampleRate)
# 语音识别模型对象
# hotWord为需要优先识别的热词
# 输入"秦剑 无憾"表示优先匹配该字符串中的字符
self.asr = ASR(prompt=hotWord)
self.saveAudio = saveAudio
self.savePath = "output"
def run(self) -> None:
self.listen()
def stop(self) -> None:
self.running = False
def 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("{} {}".format(nowTime, text))
elif type == "text":
self.infoSignal.emit("{} {}".format(nowTime, text))
else:
self.infoSignal.emit("{} {}".format(nowTime, text))
def listen(self) -> None:
"""
语音监听函数
"""
try:
with self.mic as source:
self.setInfo("录音开始")
self.running = True
while self.running:
# 设备监控
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("未检测到有效音频输入设备!!!", type='warning')
break
self.rec.adjust_for_ambient_noise(source, duration=self.duration)
self.setInfo("正在录音")
# self.running为否无法立即退出该函数,如果想立即退出则需要重写该函数
audio = self.rec.listen(source, phrase_time_limit=self.phraseTime)
# 将音频二进制数据转换为numpy类型
audionp = self.bytes2np(audio.frame_data)
if self.saveAudio:
self.saveWav(audio)
# 判断音频rms值是否超过经验阈值,如果没超过表明为环境噪声
if np.sqrt(np.mean(audionp ** 2)) < 0.02:
continue
self.setInfo("音频正在识别")
# 识别语音
result = self.asr.predict(audionp)
self.setInfo(f"识别结果为:{result}", "text")
except Exception as e:
self.setInfo(e, "warning")
finally:
self.setInfo("录音停止")
self.running = False
def bytes2np(self, inp: bytes, sampleWidth: int = 2) -> np.ndarray:
"""
将音频二进制数据转换为numpy类型
:param inp: 输入音频二进制流
:param sampleWidth: 音频采样宽度
:return: 音频numpy数组
"""
# 使用np.frombuffer函数将字节序列转换为numpy数组
tmp = np.frombuffer(inp, dtype=www.sychzs.cn16 if sampleWidth == 2 else www.sychzs.cn8)
# 确保tmp为numpy数组
tmp = np.asarray(tmp)
# 获取tmp数组元素的数据类型信息
i = np.iinfo(tmp.dtype)
# 计算tmp元素的绝对最大值
absmax = 2 ** (i.bits - 1)
# 计算tmp元素的偏移量
offset = i.min + absmax
# 将tmp数组元素转换为浮点型,并进行归一化
array = np.frombuffer((tmp.astype(np.float32) - offset) / absmax, dtype=np.float32)
# 返回转换后的numpy数组
return array
def saveWav(self, audio: sr.AudioData) -> None:
"""
保存语音结果
:param audio: AudioData音频对象
"""
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())

语音识别类

语音识别类利用Whisper进行语音识别。在使用Whisper进行语音识别时,可以通过设置initial_prompt参数来指定初始提示。initial_prompt参数是一个字符串,用于在模型生成文本之前提供一些初始的上下文信息。将这些信息传递给Whisper模型可以帮助它更好地理解任务的背景和上下文。通过设置适当的initial_prompt,可以引导模型产生与特定主题相关的响应或者在对话中提供一些先验知识。例如,热点词汇识别,结果为简体字还是繁体字。initial_prompt并不是必需的参数,如果没有适当的初始提示,可以选择不使用它,让模型完全自由生成响应。但是要注意的是如果输入的语音为环境噪声或者使用的是小型Whisper模型,initial_prompt的设置可能会导致语音识别输出结果为initial_prompt。

Whisper提供了5种型号的模型,其中4种支持纯英文版本,以平衡速度和准确性。Whisper模型越大精度越高,速度越慢,本文默认使用small型号的模型。以下是这些可用模型的型号名称、大致的显存要求和相对速度:

型号 参数量 仅英文模型 多语言模型 所需显存 相对速度
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
# www.sychzs.cn
import whisper
import numpy as np
class ASR:
"""
语音识别模型类
"""
def __init__(self, modelType: str = "small", prompt: str = ""):
"""
:param modelType: whisper模型类型
:param prompt: 提示词
"""
# 模型默认使用cuda运行,没gpu跑模型很慢。
# 使用device="cpu"即可改为cpu运行
self.model = whisper.load_model(modelType, device="cuda")
# prompt作用就是提示模型输出指定类型的文字
# 这里使用简体中文就是告诉模型尽可能输出简体中文的识别结果
self.prompt = "简体中文" + prompt
def predict(self, audio: np.ndarray) -> str:
"""
语音识别
:param audio: 输入的numpy音频数组
:return: 输出识别的字符串结果
"""
# prompt在whisper中用法是作为transformer模型交叉注意力模块的初始值。transformer为自回归模型,会逐个生成识别文字,
# 如果输入的语音为空,initial_prompt的设置可能会导致语音识别输出结果为initial_prompt
result = self.model.transcribe(audio.astype(np.float32), initial_prompt=self.prompt)
return result["text"]

4 参考

  • 数字音频基础­­­­­­­­­­从PCM说起
  • portaudio
  • PyAudio
  • pyaudio声卡信息中hostApi
  • speech_recognition
  • Whisper
  • Whisper语音转文字手把手教程

相关文章