前言
结合前段时间我们做的音频编解码器,这样我们就可以将获取到的ADPCM数据,转换成PCM数据,然后播放出来,得到一个完整的音频数据,因此,接下来几篇文章中,我们想做一个播放PCM格式的音频播放器。待到音频编解码器完成,播放器完成,或许我们就能做一个语音识别功能模块,通过一个小收音器的BLE蓝牙连接,连接到电脑上,实时完成音频传输,解码,以及识别的整套流程。可以完成语音写入文本,不仅彻底解放双手,还能解放双脚,让你离开电脑桌也能输入文本(不过也是作者的美好幻想, 先挖个坑,距离实现遥遥无期,尽请期待!)
在此之前呢,我们先脚踏实地的,了解一下我们必要的第三方库,PyAudio
。
前置知识
在介绍PyAudio
库之前我们需要先了解一点点前置内容
- 采样率:采样率是指机器每秒采样的次数,是音频处理中的重要参数。如:采样率为44100,即1秒钟采样44100次,常见的采样率为44100、48000,在小型设备中,如只专注语音的识别,不在乎音质,通常会选用更小的采样率,如8000、16000等,换取硬件设备的成本优势
- 音频格式:指的是每个采样点的长度,如:
paInt16
,表示每个采样点为16比特,一次采样获取的数据为16比特,常见的采样点大小为8、16、32等。单位为比特。通常采样率与采样点大小即可确定一定时间内,采样获取的数据大小,如:当音频格式为paInt16
,采样率为8KHz,那么1s中,将采样到8000 * 16bit = 128kb
的数据。 - 通道:指的是声道,麦克风拾音的过程。分为单声道,多声道,立体声,联合立体声等。
- 块:块是指数据块,在PyAudio可以实时处理动态音频数据,也就是
音频流
,而处理音频流实际上也是将连续
的(实际上并非连续)输入以数据块的形式处理,并不是来一个字节处理一个字节,可以理解为缓冲区buffer
的概念。
稍稍理解这些概念,以便后续阅读中看懂PyAudio
的参数,如果没懂,可以返回来反复阅读。
PyAudio
PyAudio
是一个用于在 Python 中进行音频处理的库,它为我们提供了跨平台的接口,用于录音、播放和处理音频流。PyAudio
封装了 PortAudio 库,PortAudio 是一个跨平台的音频库,支持多种操作系统(如 Windows、macOS、Linux)。
PyAudio的常见用途
- 录音(Recording Audio):可以从麦克风获取音频数据。
- 播放音频(Playing Audio):可以播放 WAV 或其他格式的音频文件。
- 实时音频处理:处理音频数据流(例如,分析麦克风输入,实时音效等)。
安装 PyAudio
首先需要安装 PyAudio
。可以使用 pip
来安装:
python">pip install pyaudio
PyAudio的核心概念
- 流(Stream): PyAudio 的核心概念是“流”(Stream)。流是数据的一个持续流动的通道,用于音频输入或输出。你可以创建一个输入流(录音)或输出流(播放音频),然后使用流来传输音频数据。
- 音频格式和数据: PyAudio 支持多种音频格式,例如
paInt16
(16 位整数格式)和paFloat32
(32 位浮点格式)。音频数据通常是原始的字节流数据,可以通过 NumPy 数组或其他方式进行处理。 - 回调函数: 在实时音频流的场景中,通常使用回调函数来处理音频数据。回调函数会在每次采集到一块音频数据时被调用。
主要功能
-
录音示例: 以下是一个使用
PyAudio
录音的简单示例:python">import pyaudio import wave# 设置录音参数 FORMAT = pyaudio.paInt16 # 音频格式 CHANNELS = 1 # 单声道 RATE = 44100 # 采样率 CHUNK = 1024 # 每次读取的数据块大小 RECORD_SECONDS = 5 # 录音时长 OUTPUT_FILENAME = "output.wav" # 输出文件p = pyaudio.PyAudio()# 开启输入流 stream = p.open(format=FORMAT,channels=CHANNELS,rate=RATE,input=True,frames_per_buffer=CHUNK)print("开始录音...")frames = []# 录制数据 for _ in range(0, int(RATE * RECORD_SECONDS / CHUNK)):data = stream.read(CHUNK)frames.append(data)print("录音结束...")# 停止流并关闭 stream.stop_stream() stream.close() p.terminate()# 将录音保存为WAV文件 wf = wave.open(OUTPUT_FILENAME, 'wb') wf.setnchannels(CHANNELS) wf.setsampwidth(p.get_sample_size(FORMAT)) wf.setframerate(RATE) wf.writeframes(b''.join(frames)) wf.close()
这里有值得一提的一个点:
python"># 录制数据 for _ in range(0, int(RATE * RECORD_SECONDS / CHUNK)):data = stream.read(CHUNK)frames.append(data)
这个循环的主要目的是从流中读取数据,并添加到
frams
列表中,可以看到RATE * RECORD_SECONDS
为采样率 * 录音时长
,这可以得到在本次录音中一共采样的次数,/ CHUNK
表明采样的数据被分块读取并写入frames
。这里其实隐式的处理了采样格式,即paInt16
。 -
播放音频示例: 以下是一个播放 WAV 文件的简单示例:
python">import pyaudio import wave# 打开WAV文件 wf = wave.open('output.wav', 'rb')p = pyaudio.PyAudio()# 打开输出流 stream = p.open(format=pyaudio.paInt16,channels=wf.getnchannels(),rate=wf.getframerate(),output=True)# 读取文件并播放 chunk = 1024 data = wf.readframes(chunk) while data:stream.write(data)data = wf.readframes(chunk)# 关闭流 stream.stop_stream() stream.close() p.terminate()
PyAudio的高级功能
-
回调模式(Callback Mode): PyAudio 提供了回调模式来处理音频流。在这种模式下,你可以通过定义回调函数来实时处理录制或播放的音频数据。例如,你可以实时对音频数据进行处理或应用某些效果(如滤波、增益等)。
示例:
python">import pyaudiodef callback(in_data, frame_count, time_info, status):# 处理音频数据print("Recording...")return (in_data, pyaudio.paContinue)p = pyaudio.PyAudio()# 打开输入流 stream = p.open(format=pyaudio.paInt16,channels=1,rate=44100,input=True,frames_per_buffer=1024,stream_callback=callback)stream.start_stream()# 等待流结束 while stream.is_active():passstream.stop_stream() stream.close() p.terminate()
PyAudio的应用场景
- 音频录制与播放:用来创建简单的音频应用程序,进行声音的录制和回放。
- 语音识别:结合其他语音识别库(如
speech_recognition
)可以实现语音识别功能。 - 实时音频处理:可以实时分析、修改或增强音频数据。
- 声音特效处理:可以在录制或播放过程中加入音效,如回声、变调等。
PyAudio与其他音频库对比
- vs. wave:
wave
模块主要用于处理静态的音频文件,而PyAudio
支持音频流,能够进行实时录音与播放。 - vs. soundfile:
soundfile
更适用于处理已保存的音频文件的读写操作,而PyAudio
更擅长实时音频流的处理。
注意事项
- 在不同平台上安装
PyAudio
可能需要额外的步骤。例如,Windows 上可能需要安装PortAudio
的依赖项。 PyAudio
并不支持直接播放 MP3 等压缩音频格式。如果需要播放其他格式的音频,可能需要使用其他库如pygame
或pydub
。
小结
总的来说,PyAudio
是一个非常强大的库,适合用于音频处理和实时音频应用,尤其在需要直接与麦克风或扬声器交互时,提供了非常简洁的接口。
stop_stream与start_stream
可能会有同学好奇,在上面的示例代码中,为什么只看见
stream.stop_stream
而没有看见stream.start_stream
呢,如果只有停止流的操作,为什么没有开启流的操作,流在哪里被开启了呢?
PyAudio
中的 stream.start_stream()
和 stream.stop_stream()
是两个重要的流控制方法,用于控制音频流的开始和停止。
在 PyAudio
的流(Stream
)中,start_stream()
和 stop_stream()
方法实际上是可选的,它们的行为是与音频流的实际使用场景有关的。让我们详细解释一下这两个方法,以及为什么在某些情况下,可能看不到显式调用 start_stream()
。
1. stream.start_stream() 的作用
start_stream()
方法用于启动音频流,开始录制或播放音频。它是用来显式告诉程序 “现在开始处理音频数据”。
2. stream.stop_stream() 的作用
stop_stream()
方法用于停止音频流,表示录制或播放过程结束。这对于需要显式控制音频流的结束时机的程序非常有用。
为什么 start_stream()
有时没被显式调用
-
回调模式(Callback Mode): 如果使用了回调模式(即通过设置
stream_callback
参数来传递回调函数),那么在调用open()
方法时,音频流已经自动开始了。回调机制会在后台处理音频数据的输入和输出。因此,在这种模式下,start_stream()
是不必要的,因为流会自动启动。例如:
python">import pyaudiodef callback(in_data, frame_count, time_info, status):# 处理音频数据print("Recording...")return (in_data, pyaudio.paContinue)p = pyaudio.PyAudio()# 打开输入流并设置回调函数 stream = p.open(format=pyaudio.paInt16,channels=1,rate=44100,input=True,frames_per_buffer=1024,stream_callback=callback)# 注意: 这里不需要显式调用 start_stream() stream.start_stream() # 这行可以省略,回调模式会自动启动# 等待流结束 while stream.is_active():passstream.stop_stream() stream.close() p.terminate()
在这个例子中,
start_stream()
虽然可以显式调用,但在使用回调模式时,可以省略,因为PyAudio
会在底层自动处理流的启动和停止。 -
默认自动启动(在非回调模式下): 在非回调模式(例如通过
stream.read()
或stream.write()
进行音频数据的读取和写入)中,start_stream()
也是隐式调用的。在创建音频流时,open()
方法会自动启动流,并开始数据的录制或播放。python">import pyaudiop = pyaudio.PyAudio()# 设置流参数 stream = p.open(format=pyaudio.paInt16,channels=1,rate=44100,input=True,frames_per_buffer=1024)# 此时,流已经自动启动 data = stream.read(1024) # 录音 stream.stop_stream() stream.close() p.terminate()
在这个例子中,
open()
已经自动启动了流,调用stream.read()
只是触发音频数据的读取过程,而不需要显式调用start_stream()
。
小结
- 回调模式:当你使用回调函数时,
PyAudio
会自动管理音频流的开始和停止。因此,通常不需要显式调用start_stream()
,而只需要设置stream_callback
并开始流的处理。 - 非回调模式:即便是非回调模式,
open()
方法有时也会自动开始音频流,尤其是在调用stream.read()
或stream.write()
时,不需要显式调用start_stream()
。
但是,在一些场景下,尤其是自定义更复杂的音频控制时,start_stream()
和 stop_stream()
还是很有用的,特别是当你需要手动控制音频流的启动与停止时。
谁开启的麦克风或扬声器
在上面的示例代码中,敏锐的读者可能会发现,似乎没有发现那里调用了麦克风或扬声器,合理猜测,直接告诉我们,应该是PyAudio库调用了硬件设备,但真是如此吗,如果是,那么又是怎么调用的呢?
我们以上面的代码为例,究竟是谁调用了麦克风,开启了录音
音频的录制是通过 pyaudio
库来实现的,调用麦克风硬件的部分是由 pyaudio
库中的 PyAudio
类和它的 open()
方法来完成的。具体的过程是这样的:
调用麦克风硬件的关键步骤:
-
pyaudio.PyAudio()
: 这个步骤创建了一个PyAudio
对象。这个对象是与音频硬件交互的主要接口,它会管理音频流(包括录音流和播放流),并负责配置音频设备。 -
p.open()
:open()
方法是用来打开一个音频流的,这个流可以是输入流(例如录音)或者输出流(例如播放音频)。在上面的代码中,p.open()
被用来打开一个输入流,用于录制音频。python">stream = p.open(format=FORMAT,channels=CHANNELS,rate=RATE,input=True,frames_per_buffer=CHUNK)
关键参数:
format=FORMAT
:指定音频数据的格式,通常为 16 位 PCM 编码(pyaudio.paInt16
)。channels=CHANNELS
:指定录音的通道数,1 表示单声道,2 表示立体声。rate=RATE
:指定采样率,通常选择 44100 Hz,这是 CD 音质的标准采样率。input=True
:这告诉pyaudio
创建一个输入流,也就是通过麦克风录音。如果将其设置为False
,则会创建输出流,用于播放音频。frames_per_buffer=CHUNK
:指定每次读取的音频块的大小,通常是 1024 或 2048。
此时,
pyaudio
会通过系统的音频接口与麦克风硬件进行连接,并开始接收音频数据。具体而言,pyaudio
会通过操作系统调用音频驱动程序来访问麦克风硬件。操作系统(如 Windows、Linux、macOS)会提供接口给pyaudio
来处理与硬件的交互。 -
stream.read(CHUNK)
:stream.read(CHUNK)
方法是用来从音频流中读取音频数据的。这里的CHUNK
表示每次读取的音频数据的大小,也就是说它会以一定的时间间隔(根据CHUNK
的大小)从麦克风捕捉音频数据。通过连续调用
stream.read()
,程序不断地从麦克风获取音频数据,并将这些数据存储到frames
列表中。
总结:
- 谁调用了麦克风硬件?
实际上是pyaudio
库调用了麦克风硬件。pyaudio
作为一个高层的音频处理库,封装了与操作系统音频接口的交互。操作系统的音频驱动程序在幕后管理与麦克风硬件的连接和数据传输。 - 如何调用的?
- 通过
pyaudio.PyAudio()
创建的 PyAudio 实例。 - 使用
p.open()
方法创建音频输入流(input=True
),并通过该流接收来自麦克风的音频数据。 - 最终使用
stream.read()
不断地从麦克风获取音频样本。
- 通过
这样,pyaudio
库就可以通过操作系统和驱动程序,控制麦克风硬件进行录音了。