PyQt实战——实现可视化音频播放器(十三)

系类往期文章:
PyQt5实战——多脚本集合包,前言与环境配置(一)
PyQt5实战——多脚本集合包,UI以及工程布局(二)
PyQt5实战——多脚本集合包,程序入口QMainWindow(三)
PyQt5实战——操作台打印重定向,主界面以及stacklayout使用(四)
PyQt5实战——UTF-8编码器UI页面设计以及按钮连接(五)
PyQt5实战——UTF-8编码器功能的实现(六)
PyQt5实战——翻译器的UI页面设计以及代码实现(七)
PyQt5实战——翻译的实现,第一次爬取微软翻译经验总结(八)
PyQt5实战——翻译的实现,成功爬取微软翻译(可长期使用)经验总结(九)
PyQt实战——使用python提取JSON数据(十)
PyQt实战——随机涂格子的特色进度条(十一)
PyQt实战——实现编码器与进度条之间的通信,使进度条反映编码进度(十二)

前言

通过上一篇文章,我们已经大概了解了PyAudio是一个什么样的库以及给出了相应的示例代码,那么在本文中,我们就要使用PyAudio库,来实现音频的播放,同时呢我们将加上matplotlib库,来绘制音频的波形。通过阅读本文,你将了解到:如何使用PyAudio,将音频文件传入PyAudio,如何绘制音频波形,以及音频波形与音频播放的同步,如何避免音频卡顿问题。

展示

请添加图片描述

播放器思想

  • 首先,我们要通过PyAudio来实现音频的播放功能,通过读取PCM文件,将音频输出给外设(耳机,扬声器等)。
  • 此外,在音频输出的过程中,同时绘制当前读取数据块的折线图。
  • 在当前数据块被读取时,折线图将绘制完成,当数据块被读取完时,数据块所表示的音频将被播放完。
  • 将下一数据块的音频数据读取,然后重复上面的操作
  • __init__:在对象初始化时,将一些PyAudio的参数初始化完成,比如采样率,声道,采样点大小,数据块大小等。并创建画布
  • play:播放方法,关闭上一个音频流,如果有的话,打开音频流,开一个线程来执行播放与绘画
  • load_pcm_audio:从pcm文件中读取数据
  • play_audio:在音频流中写入数据块,并重新绘制折线图
  • update_parameters:更新播放器的参数,如采样率,声道等
  • closeEvent:关闭音频流,释放系统资源

代码展示

下面是 音频播放器的代码,而非播放器UI的代码:

from random import sample
import sys
import numpy as np
import pyaudio
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QFileDialog
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import threadingclass AudioPlayer(QWidget):def __init__(self,QWidget):super().__init__()self.file = ''self.layout = QVBoxLayout()self.setLayout(self.layout)self.figure = plt.Figure()self.canvas = FigureCanvas(self.figure)self.layout.addWidget(self.canvas)# 初始化PyAudioself.p = pyaudio.PyAudio()# 设置播放参数self.sample_rate = 16000  # 采样率self.channels = 1         # 单声道self.sample_width = 2     # 16位深度self.chunk_size = 1024    # 每次播放的PCM数据块大小# 波形更新self.x_data = np.arange(self.chunk_size)self.y_data = np.zeros(self.chunk_size)self.plot = self.figure.add_subplot(111)self.line, = self.plot.plot(self.x_data, self.y_data)# 去掉坐标轴self.plot.axis('off')# 显式设置坐标轴范围,确保波形线延伸到整个画布self.plot.set_xlim(0, self.chunk_size)  # 设置x轴范围为数据块大小self.plot.set_ylim(-1, 1)  # 设置y轴范围为[-1, 1],16-bit PCM数据的常规范围self.audio_data = Noneself.index = 0self.stream = Nonedef play(self,filename):if self.file != filename:self.file = filenameself.load_pcm_audio(filename)self.index = 0  # 重置播放进度# 如果audio_data为空,或者没有选择音频,直接返回if self.audio_data is None:print("请先选择一个音频文件!")return# 每次播放前重置播放进度和音频流self.index = 0# 如果stream已经存在且正在播放,先停止它if self.stream is not None and self.stream.is_active():self.stream.stop_stream()self.stream.close()# 打开音频流if self.sample_width == 2:samplewith = pyaudio.paInt16elif self.sample_width == 4:samplewith = pyaudio.paInt32self.stream = self.p.open(format=samplewith,channels=self.channels,rate=self.sample_rate,output=True,frames_per_buffer=self.chunk_size)# 启动一个单独的线程来播放音频threading.Thread(target=self.play_audio, daemon=True).start()def load_pcm_audio(self, filename):# 读取PCM音频文件with open(filename, 'rb') as f:self.audio_data = np.frombuffer(f.read(), dtype=np.int16)def play_audio(self):while self.index < len(self.audio_data):chunk = self.audio_data[self.index:self.index + self.chunk_size]if len(chunk) < self.chunk_size and len(chunk) > 0:chunk = np.pad(chunk, (0, self.chunk_size - len(chunk)), 'constant')self.index += self.chunk_sizeself.stream.write(chunk.tobytes())# 更新波形数据self.y_data = chunk / 32768.0  # 16-bit PCM音频数据范围 [-1, 1]self.line.set_ydata(self.y_data)self.canvas.draw()def update_parameters(self, sample_rate, channels, sample_width):self.sample_rate = sample_rateself.channels = channelsself.sample_width = sample_widthdef closeEvent(self, event):if self.stream is not None:self.stream.stop_stream()self.stream.close()self.p.terminate()event.accept()

下面给出代码的详细解释:

_init_

  • 创建画布,绘制图像,并将画布加入到layout
  • 初始化PyAudio
  • 设置播放参数,默认16K采样率,单声道,16比特采样点,数据块大小为1024
  • 初始化坐标轴,在画布中,X轴不变,Y会随着数据块的更新而变化,每次更新折线图便会发生一次改变
  • 去掉坐标轴(美观)
  • X轴的范围是[0,1024],即一个数据块的大小,Y轴的范围是[-1,1],这是16bit PCM数据的常规范围
  • 初始化音频数据变量,索引值,音频流等

play

  • 如果当前的文件与重新选择的文件不一致,则会重新加载音频文件,如果没有音频文件,则会返回错误
  • 重置播放进度
  • 如果当前有音频流存在,暂停并结束它
  • 重新打开音频流
  • 单独开一个线程来执行播放音频和绘制波形

load_pcm_audio

  • 从文件中读取数据,我们来详细解释一下self.audio_data = np.frombuffer(f.read(), dtype=npint16)
  1. f.read()
  • f 是一个文件对象,通常是通过 open 打开文件后得到的。
  • f.read()读取文件内容,并将其作为一个字节串(bytes)返回。
    • 如果文件是二进制文件(比如 .wav.mp3 格式的音频文件),f.read() 会读取文件的所有字节内容。
    • f.read() 返回的数据是一个包含音频原始二进制数据的字节序列。
  1. np.frombuffer()
  • np.frombuffer()NumPy 提供的一个函数,用于从缓冲区(字节串、字节流等)中创建一个 NumPy 数组。
  • 它将原始的二进制数据解释为指定数据类型的数组。
  • 该函数通常用于将文件中的二进制数据(如音频文件)转换为 NumPy 数组,方便进一步处理。
  1. dtype=np.int16
  • dtype参数指定了生成的NumPy数组的数据类型。在这里,dtype=np.int16表示将字节数据转换为 16 位整数(int16)。
    • np.int16 表示每个数据元素是一个 16 位有符号整数(即每个值占 2 个字节)。
    • 16 位整数常用于表示音频数据,因为音频信号通常是通过这种方式存储的,特别是当音频使用 PCM(脉冲编码调制)格式时。
  1. self.audio_data
  • 这段代码将np.frombuffer()返回的NumPy数组赋值给self.audio_dataself.audio_data用于存储读取的音频数据。
    • 该数组的元素是从音频文件中读取的 PCM 数据,每个元素是一个 16 位整数,表示音频样本的幅度值。

play_audio

  • 判断index当前音频的进度,如果还没读取完数据,则将audio_data中的数据分块传给chunk
  • 如果chunk的长度小于1024,说明audio_data已经到了最后一个数据块,且大小不等于1024,因此需要在chunk后面补零
  • 更新index音频播放进度
  • chunk数据块的数据写入音频流中
  • chunk数据缩放到[-1,1]中
  • 绘制折线图

这里值得注意的是,为什么当chunk不足1024时,需要啊在chunk后面补零呢,是画布的X轴大小为1024,如果Y轴的数据没有1024个,则无法完成绘画

update_parameters

  • 更新参数方法,供上层UI界面调用

closeEvent

  • 关闭音频流且释放系统资源

值得注意的是,closeEvent方法通常是在窗口关闭事件发生时自动被调用的,它是与窗口或界面关闭相关联的事件处理函数。

event.accept方法是用来标记事件已被处理,表示允许窗口关闭(即立即销毁窗口并退出程序)。如果你不调用event.accept窗口,窗口的关闭可能会被组织或无效。

音频波形与音频播放的同步

如果要实现音频的可视化,音频播放与波形绘制的同步时必不可少的操作,在这里,我们通过PyAudio的流式操作,将音频数据分成数据块来读取并播放,这样的操作思想为实现波形绘制与音频播放的同步奠定了基础。

在每一个音频数据块被读取时,我们将数据块交给PyAudio播放的同时,也制作数据进行波形绘制。这样确保了绘制与播放操作的是一个数据块,不会出现速度不一的情况。

主要的实现在play_audio方法中:

    def play_audio(self):while self.index < len(self.audio_data):chunk = self.audio_data[self.index:self.index + self.chunk_size]if len(chunk) < self.chunk_size and len(chunk) > 0:chunk = np.pad(chunk, (0, self.chunk_size - len(chunk)), 'constant')self.index += self.chunk_sizeself.stream.write(chunk.tobytes())# 更新波形数据self.y_data = chunk / 32768.0  # 16-bit PCM音频数据范围 [-1, 1]self.line.set_ydata(self.y_data)self.canvas.draw()

如何避免音频卡顿

在笔者第一次使用PyAudio的功能时,为了实现播放功能,而没有深入了解PyAudio的运行原理,导致在第一次播放时正常,再重复播放几次后会出现卡顿情况,播放次数越多卡顿越厉害,到最后,音频的波形只有几帧了,随后笔者开始对如何实现PyAudio展开了优化,上面是优化后的代码,下面给出优化的心路历程。

UI线程与音频线程的阻塞问题

音频播放是一个需要实时更新的过程,可能会造成UI线程和音频线程的冲突。特别是音频播放涉及IO操作,如果UI更新阻塞了音频播放,可能会导致卡顿。

修改后,通过开子线程的方式,将UI线程与音频线程区分开,使音频播放独立于UI线程执行。

内存管理问题

在之前的代码中,每一次点击播放,都会重新调用一次load_pcm_audio,会重新将整个PCM文件保存在self.audio_data中,如果文件较大,这会消耗大量的内存,尤其是当多次播放时,音频数据会反复加载,可能会导致内存积累,引起卡顿。

修改后,仅有当检测读取文件与上一次读取文件不一致时,才会调用load_pcm_audio,否则将不重新加载PCM文件,直接使用已有数据,使用self.index来控制播放进度。

PyAudio流的管理问题

在之前的代码中,初始化self.p = pyaudio.PyAudio()是放在play方法中,这会导致,每次按键调用play方法时,stream对象就会被重复创建且播放时没有正确地停止和清理,导致音频流的资源没有得到释放,影响性能。

修改后,stream对象在初始化时便被创建,往后在play方法时不重复创建,在结束时被释放。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/496830.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java日志框架:log4j、log4j2、logback

文章目录 配置文件相关1. properties测试 2. XMl使用Dom4j解析XML Log4j与Log4j2日志门面 一、Log4j1.1 Logges1.2 Appenders1.3 Layouts1.4 使用1.5 配置文件详解1.5.1 配置根目录1.5.2 配置日志信息输出目的地Appender1.5.3 输出格式设置 二、Log4j22.1 XML配置文件解析2.2 使…

RustDesk内置ID服务器,Key教程

RustDesk内置ID服务器&#xff0c;Key教程 首先需要准备一个域名&#xff0c;并将其指定到你的 rustdesk 服务器 ip 地址上&#xff0c;这里编译采用的是Github Actions &#xff0c;说白了是就workflows&#xff0c;可以创建一些自动化的工作流程&#xff0c;例如代码的检查&a…

OpenHarmony怎么修改DPI密度值?RK3566鸿蒙开发板演示

本文介绍在开源鸿蒙OpenHarmony系统下&#xff0c;修改DPI密度值的方法&#xff0c;触觉智能Purple Pi OH鸿蒙开发板演示&#xff0c;搭载了瑞芯微RK3566四核处理器&#xff0c;Laval鸿蒙社区推荐开发板&#xff0c;已适配全新开源鸿蒙OpenHarmony5.0 Release系统&#xff0c;适…

OCR实践-Table-Transformer

前言 书接上文 OCR实践—PaddleOCR Table-Transformer 与 PubTables-1M table-transformer&#xff0c;来自微软&#xff0c;基于Detr&#xff0c;在PubTables1M 数据集上进行训练&#xff0c;模型是在提出数据集同时的工作&#xff0c; paper PubTables-1M: Towards comp…

常见的邮件协议SMTP和POP3

常见的邮件协议包括SMTP和POP3&#xff0c;SMTP用来发送邮件&#xff0c;POP3用来接收邮件信息。 SMTP SMTP 是一种用于发送电子邮件的协议。它的主要作用是将**电子邮件**从邮件客户端&#xff08;如 Outlook、Thunderbird&#xff09;或邮件服务器发送到接收服务器。 SMTP …

UGUI源码分析 --- UI的更新入口

首先所有的UI组件都是添加到画布&#xff08;Canvas&#xff09;显示的&#xff0c;所以首先要从Canvas入手&#xff0c;通过搜索脚本函数以及使用Profiler查看UI的函数的执行&#xff0c;定位到了willRenderCanvases函数 打开UI的文件夹&#xff0c; 通过搜索willRenderCanvas…

Wend看源码-Java-集合学习(Set)

概述 Wend看源码-Java-集合学习(List)-CSDN博客 在上一篇文章中&#xff0c;我们深入探讨了Java集合框架的父类以及List集合的细节。接下来&#xff0c;本文将重点阐述Java中的Set集合&#xff0c;包括其内部的数据结构以及核心方法的详尽说明。 Set 集合 图1 java-Set类型数据…

双闭环直流调速系统

一 设计要求 1、原始条件 主要参数&#xff1a;直流电机PN 22KW&#xff0c;额定电压UN220V&#xff0c; 额定电流IN106A&#xff0c;nN 1500r/min&#xff0c;电枢绕组电阻Ra 0.11Ω&#xff0c;主电路总电阻R0.32Ω&#xff0c;磁极对数P2&#xff0c; Ks22&#xff0c;GD2…

word无法创建工作文件,检查临时环境变量。

word无法创建工作文件&#xff0c;检查临时环境变量。 word preview版本&#xff0c;关联打开文件出现报错。word无法创建工作文件&#xff0c;检查临时环境变量。 打开注册表&#xff0c;删除键 Word Preview: HKCR\CLSID{84F66100-FF7C-4fb4-B0C0-02CD7FB668FE} PowerPoint …

Excel将混乱的多行做成1列

目标是将数据按从左到右&#xff0c;再从上到下排成一列。 公式法 首先用textjoin函数将文本包起来&#xff0c;做成一个超长文本。 然后用公式 截取文本 Mid(m1,n,3)&#xff0c;意思就是对m1单元格&#xff0c;从第n个字符开始&#xff0c;截取3个字符出来。 这个公式如何自…

深入解析MySQL索引结构:从数组到B+树的演变与优化

前言&#xff1a; 在数据库查询中&#xff0c;索引是一种关键的性能优化工具。然而&#xff0c;索引的失效可能导致查询效率大幅下降。为了更好地理解索引的工作原理及规避其失效&#xff0c;深入了解索引结构的演变过程尤为重要。 MySQL 的索引数据结构从简单到复杂&#xff0…

怎么把多个PDF合并到一起-免费实用PDF编辑处理工具分享

>>更多PDF文件处理应用技巧请前往 96缔盟PDF处理器 主页 查阅&#xff01; 序言 我之前的文章也有介绍过如何使用96缔盟PDF处理器对PDF文件合并或者批量合并的介绍&#xff0c;但是当时是使用DMPDFUtilTool1.0版本进行的&#xff0c;当时的功能尚不完善&#xff0c;还不支…

medical meadow medical flashcards

“medalpaca/medical_meadow_medical_flashcards” 是一个在 Hugging Face 数据集平台上可用的数据集。这个数据集主要面向医学领域&#xff0c;包含了大量的医学知识卡片&#xff0c;这些卡片由医学生创建和更新&#xff0c;旨在帮助学习和记忆重要的医学概念。以下是关于这个…

新品:SA628F39大功率全双工音频传输模块

SA628F39是一款高集成度的8W大功率全双工无线数据语音一体通话模块&#xff0c;专为高效、稳定的远程通信设计。该模块内置高速微控制器、高性能射频芯片、功率放大器、ESD静电保护和硬件看门狗芯片&#xff0c;具备反接保护、过流过压保护和防死机保护等多重安全功能&#xff…

moviepy将图片序列制作成视频并加载字幕 - python 实现

DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” -------------------------------------------------------------…

面试突击-JAVA集合类(持续更新...)

前言 这篇文档非常适合面试突击人群&#xff0c;java集合类是面试高频问点&#xff0c;阅读完此文章可以直接应对面试官一切问题&#xff0c;最终吊打面试官。 概览 Java 集合&#xff0c;也叫作容器&#xff0c;主要是由两大接口派生而来&#xff1a;一个是 Collection接口&am…

如何计算相位差

如何计算相位差 假设我们有两个同频率的正弦信号&#xff1a; 这里两个信号的角频率w2πf是相同的&#xff0c;根据同频正弦信号相位差的计算方法&#xff0c;直接用两个信号的相位相减。 再来看利用波形图计算相位差的例子&#xff1a; 另一种计算方式&#xff1a;

龙智出席2024零跑智能汽车技术论坛,分享功能安全、需求管理、版本管理、代码扫描等DevSecOps落地实践

龙智快讯 2024年12月5日&#xff0c;由零跑和盖世汽车主办的“2024零跑智能汽车技术论坛”在杭州零跑总部圆满落幕。此次技术论坛聚焦AI语言大模型、AUTOSAR AP平台、DevOps、端到端自动驾驶等热点话题展开探讨&#xff0c;旨在推动智能汽车技术的创新与发展。 龙智作为国内领先…

剑指Offer|LCR 014. 字符串的排列

LCR 014. 字符串的排列 给定两个字符串 s1 和 s2&#xff0c;写一个函数来判断 s2 是否包含 s1 的某个变位词。 换句话说&#xff0c;第一个字符串的排列之一是第二个字符串的 子串 。 示例 1&#xff1a; 输入: s1 "ab" s2 "eidbaooo" 输出: True 解…

LabVIEW条件配置对话框

条件配置对话框&#xff08;Configure Condition Dialog Box&#xff09; 要求&#xff1a;Base Development System 当右键单击**条件禁用结构&#xff08;Conditional Disable Structure&#xff09;**并选择以下选项时&#xff0c;会显示此对话框&#xff1a; Add Subdiagr…