PyAudio库基本知识详解——为自制PCM音频播放器做准备
前言
结合前段时间我们做的音频编解码器,这样我们就可以将获取到的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 来安装:
pip install pyaudio
PyAudio的核心概念
- 流(Stream): PyAudio 的核心概念是“流”(Stream)。流是数据的一个持续流动的通道,用于音频输入或输出。你可以创建一个输入流(录音)或输出流(播放音频),然后使用流来传输音频数据。
- 音频格式和数据: PyAudio 支持多种音频格式,例如
paInt16(16 位整数格式)和paFloat32(32 位浮点格式)。音频数据通常是原始的字节流数据,可以通过 NumPy 数组或其他方式进行处理。 - 回调函数: 在实时音频流的场景中,通常使用回调函数来处理音频数据。回调函数会在每次采集到一块音频数据时被调用。
主要功能
-
录音示例: 以下是一个使用
PyAudio录音的简单示例: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()这里有值得一提的一个点:
# 录制数据 for _ in range(0, int(RATE * RECORD_SECONDS / CHUNK)):data = stream.read(CHUNK)frames.append(data)这个循环的主要目的是从流中读取数据,并添加到
frams列表中,可以看到RATE * RECORD_SECONDS为采样率 * 录音时长,这可以得到在本次录音中一共采样的次数,/ CHUNK表明采样的数据被分块读取并写入frames。这里其实隐式的处理了采样格式,即paInt16。 -
播放音频示例: 以下是一个播放 WAV 文件的简单示例:
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 提供了回调模式来处理音频流。在这种模式下,你可以通过定义回调函数来实时处理录制或播放的音频数据。例如,你可以实时对音频数据进行处理或应用某些效果(如滤波、增益等)。
示例:
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()是不必要的,因为流会自动启动。例如:
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()方法会自动启动流,并开始数据的录制或播放。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()被用来打开一个输入流,用于录制音频。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 库就可以通过操作系统和驱动程序,控制麦克风硬件进行录音了。
相关文章:
PyAudio库基本知识详解——为自制PCM音频播放器做准备
前言 结合前段时间我们做的音频编解码器,这样我们就可以将获取到的ADPCM数据,转换成PCM数据,然后播放出来,得到一个完整的音频数据,因此,接下来几篇文章中,我们想做一个播放PCM格式的音频播放器…...
Git如何添加子仓库
背景 项目中经常使用别人维护的模块,在git中使用子模块的功能能够大大提高开发效率。 使用子模块后,不必负责子模块的维护,只需要在必要的时候同步更新子模块即可。 本文主要讲解子模块相关的基础命令,详细使用请参考main page…...
001__VMware软件和ubuntu系统安装(镜像)
[ 基本难度系数 ]:★☆☆☆☆ 一、Vmware软件和Ubuntu系统说明: a、Vmware软件的说明: 官网: 历史版本: 如何下载? b、Ubuntu系统的说明: 4、linux系统的其他版本:红旗(redhat)、dibian、cent…...
在国产电脑上运行PDFSAM软件使用pdf分割合并交替混合处理pdf文档
软件下载地址: https://sourceforge.net/projects/pdfsam/files/ 需要注意事项,系统需要java环境,确认系统有java环境,根据软件版本需求安装对应的java运行环境。 下载pdfsam-4.3.4-linux.tar.gz安装包,解压,将runt…...
STM32完全学习——FATFS0.15移植SD卡
一、下载FATFS源码 大家都知道使用CubMAX可以很快的将,FATFS文件管理系统移植到单片机上,但是别的芯片没有这么好用的工具,就需要自己从官网下载源码进行移植。我们首先解决SD卡的驱动问题,然后再移植FATFS文件管理系统。 二、SD…...
Linux -- 生产消费模型之环形队列、信号量
目录 前言 环形队列 如何用环形队列实现生产消费模型? 信号量 sem_t sem_init(初始化信号量) sem_destroy(销毁信号量) 什么是PV操作? sem_wait(P操作,减少信号量ÿ…...
Ashy的考研游记
文章目录 摘要12.1112.2012.21 DAY1(政治/英语)政治英语 12.22 DAY2(数学/专业课)数学专业课 结束估分 摘要 在24年的12月里,Ashy完成了他的考研冲刺,顺利的结束了他本年度的考研之旅。 在十二月里&#…...
MySQL线上事故:使用`WHERE`条件`!=xxx`无法查询到NULL数据
前言 在一次 MySQL 的线上查询操作中,因为 ! 的特性导致未能正确查询到为 NULL 的数据,险些引发严重后果。本文将详细解析 NULL 在 SQL 中的行为,如何避免类似问题,并提供实际操作建议。 1. 为什么NULL会查询不到? 在…...
vue3学习笔记(11)-组件通信
1.props 父传子 子传夫 父传子 接收用defineProps([]) 空字符串也是假 2.自定义事件 $event:事件对象 ref定义的数据在模板里面引用的时候可以不用.value 3.子传父 宏函数 触发事件 声明事件 defineEmits() 挂载之后3s钟触发 4.命名 肉串命名 5.任意组件通信 mitt pubs…...
【PDF物流单据提取明细】批量PDF提取多个区域内容导出表格或用区域内容对文件改名,批量提取PDF物流单据单号及明细导出表格并改名的技术难点及小节
相关阅读及下载: PDF电子物流单据: 批量PDF提取多个区域局部内容重命名PDF或者将PDF多个局部内容导出表格,具体使用步骤教程和实际应用场景的说明演示https://mp.weixin.qq.com/s/uCvqHAzKglfr40YPO_SyNg?token720634989&langzh_CN扫描…...
张量与数据类型
Pytorch最基本的操作对象——张量(tensor),张量是Pytorch中重要的数据结构,可认为是一个高维数组。一般的,标量(scalar)是只有大小没有方向的量,如1、2、3等;向量&#x…...
torchvision.utils.make_grid 解释下
torchvision.utils.make_grid 是 PyTorch 中 torchvision 库提供的一个实用函数,用于将多个图像拼接成一个网格,方便进行可视化。 主要功能 make_grid 将一批图片组织成一个网格形式,输出一个单一的张量,便于使用可视化工具(如 Matplotlib)查看图像。 参数解释 torchvi…...
Android原生Widget使用步骤
需要创建三个XML文件以及一个Class文件 三个XML文件分别是 Widget布局文件 <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_p…...
实验八 指针2
7-1 利用指针返回多个函数值 分数 30 全屏浏览 切换布局 作者 陈晓梅 单位 广东外语外贸大学 读入n个整数,调用max_min()函数求这n个数中的最大值和最小值。 输入格式: 输入有两行: 第一行是n值; 第二行是n个数。 输出格式: 输出最大…...
1 数据库(下):多表设计 、多表查询 + SQL中的with查询语法(MySQL8.0以后版本才支持这种新语法)+ 数据库优化(索引优化)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、多表设计1 多表设计-概述2 三种多表关系一对多(多对一)(1)无外键约束(逻辑外键)&…...
什么是.net framework,什么是.net core,什么是.net5~8,版本对应关系
我不知道有多少人和我一样,没学习过.netCore,想要学习,但是版本号太多就蒙了,不知道学什么了,这里解释下各个版本的关系 我们一般开始学习微软的时候,都是开始学习的.netframework,常用的就是4…...
vulhub-wordpress靶场
一.主题上传漏洞 来到靶场点击主题选择add new 这里有一个上传主题的地方 我们可以去网上找到wordpress主题下载一个 wordpress模板 网页设计模板 免费 免费下载 - 爱给网 下载完成后对我们有用的东西只有这一个目录,把它拖出来 点开moban目录后,创建…...
安装与配置
《PHP Libxml》是一个在PHP中处理XML和HTML文档的重要库。它提供了丰富的API,支持DOM、SimpleXML和XMLReader等多种解析方式,广泛应用于各种编程语言和项目中。 安装与配置 安装: 在PHP中,libxml扩展通常是默认启用的。如果你需要手动安装&…...
斗鱼Android面试题及参考答案
常用的图片框架有哪些? Glide:是一个快速高效的 Android 图片加载库,专注于平滑滚动。它支持多种图片格式,包括 GIF,具有高效的缓存策略,能自动管理图片的生命周期,避免内存泄漏和 OOM 错误。其 API 简洁易用,可轻松实现图片的加载、显示和缓存等功能,如一行代码即可实…...
Could not install Gradle distribution from 的解决办法
在安装完成AndroidStudio之后,运行工程出现如下错误 Could not install Gradle distribution from https://services.gradle.org/distributions/gradle-6.5-bin.zip. 错误原因是:对应版本的Gradle文件下载失败了,我这里是gradle-6.5-bin.zip,不同版本的android studio也可…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
