gpt4 book ai didi

python - Matplotlib:音频播放器光标(垂直滑动线)的动画在 WxPython 上看起来很破损

转载 作者:太空宇宙 更新时间:2023-11-04 03:05:48 26 4
gpt4 key购买 nike

我正在构建一个项目,该项目需要用于特定目的的音频播放器。我目前正在使用 WxPython、Matplotlib 和 PyAudio 包。我还在使用 Matplotlib 的 WXAgg 后端 (backend_wxagg)。

基本思想非常简单:音频数据将绘制在主窗口上并同时通过 PyAudio 播放,同时在绘图上显示播放进度 — 我打算用动画垂直线来做(水平滑动光标),例如您在 Audacity 中看到的那种类型。我已经尝试过通用的 Matplotlib 动画示例,还有其他一些通过网络传播的示例,但它们要么太慢(没有 blitting),要么依赖于 FuncAnimation(一种不适合我的项目的架构),或者利用我正在尝试使用的技术(目前还没有用)。

屏幕上确实有东西在移动,但整体画面一团糟……在我的 Ubuntu 16 桌面上,一个白色填充矩形出现在绘图上,而 < strong>black filled rectangle 出现在我的 Mint 笔记本电脑上。尽管已经努力工作了几天并且几乎让它工作了,但现在是时候谦虚地向您寻求帮助了...:/

我坚持使用 blit() 方法,因为据我所知 (i) 允许我刷新自定义事件下的绘图(在本例中为音频帧消耗)和 ( ii) 具有良好的性能(由于数据集的大小可变,这是一个问题)。

将我的项目精简到最低限度,这里是代码,一旦处理完毕,有望让我修复整个应用程序(2000 多行):

# -*- coding: UTF-8 -*-
#

import wx
import gettext
import struct
import matplotlib
matplotlib.use('WX')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import pyaudio
import numpy as np
import time

CHUNK_SIZE = 1024 # Size (in samples) of each audio_callback reading.
BYTES_PER_FRAME = 2 # Number of bytes in each audio frame.
CHANNELS = 1 # Number of channels.
SAMPLING_RATE = 11025 # Audio sampling rate.

audio_chunks = []

# A simple frame with a tab (generated by WxGlade and simplified for example purposes):
class PlayerFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.CAPTION | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.MAXIMIZE | wx.MAXIMIZE_BOX | wx.SYSTEM_MENU | wx.RESIZE_BORDER | wx.CLIP_CHILDREN
wx.Frame.__init__(self, *args, **kwds)
self.main_notebook = wx.Notebook(self, wx.ID_ANY, style=0)
self.__set_properties()
self.__do_layout()
self.initAudio() # Initiates audio variables and event binding.
self.initPlotting() # Initiates plotting variables and widgets.
self.startPlayback() # Starts audio playback.
def __set_properties(self):
self.SetTitle(_("Audio signal plotting and playback with cursor"))
self.SetSize((720, 654))
def __do_layout(self):
sizer_main = wx.BoxSizer(wx.VERTICAL)
sizer_main.Add(self.main_notebook, 1, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 25)
self.SetSizer(sizer_main)
self.Layout()

# Audio stuff initialization:
def initAudio(self):
# Binds the playback move event to a handler:
self.Bind(EVT_PLAYBACK_MOVE, self.OnPlaybackMove)
# Creates an empty audio chunk with "CHUNK_SIZE" samples of zero value ([0, 0, ..., 0, 0]):
empty_chunk = struct.pack("<h", 0)*CHUNK_SIZE
# Initializes audio chunk array with 20 empty audio chunks:
audio_chunks.extend([empty_chunk]*20)
# Points playback_counter to the first audio chunk:
global playback_counter; playback_counter = 0

def startPlayback(self):
# Initializes audio playback:
global p; p = pyaudio.PyAudio()
global audio_stream; audio_stream = p.open ( format = p.get_format_from_width(BYTES_PER_FRAME)
, channels = CHANNELS
, rate = SAMPLING_RATE
, output = True
, stream_callback = audio_callback
, frames_per_buffer = CHUNK_SIZE )

# Plotting stuff initialization:
def initPlotting(self):
# Converts the raw audio chunks to a normal array:
samples = np.fromstring(b''.join(audio_chunks), dtype=np.int16)
# Creates plot supporting widgets:
self.pane = wx.Panel(self.main_notebook, wx.ID_ANY)
self.canvas = FigureCanvas(self.pane, wx.ID_ANY, Figure())
self.figure = self.canvas.figure
self.pane.SetMinSize((664, 355))
sizer_15 = wx.BoxSizer(wx.HORIZONTAL)
sizer_16 = wx.BoxSizer(wx.VERTICAL)
sizer_10 = wx.BoxSizer(wx.HORIZONTAL)
sizer_10.Add(self.canvas, 1, wx.EXPAND, 0)
sizer_16.Add(sizer_10, 2, wx.BOTTOM | wx.EXPAND, 25)
sizer_15.Add(sizer_16, 1, wx.ALL | wx.EXPAND, 25)
self.pane.SetSizer(sizer_15)
self.main_notebook.AddPage(self.pane, _("my_audio.wav"))

# ================================================
# Initializes plotting (is the problem in here???)
# ================================================
t = range(len(samples))
self.axes1 = self.figure.add_subplot(111)
self.axes1.set_xlim(0, len(samples))
self.axes1.set_ylim(-32768, 32767)
self.line1, = self.axes1.plot(t, samples)
self.Layout()
self.background = self.figure.canvas.copy_from_bbox(self.axes1.bbox)
self.playback_line = self.axes1.axvline(color="y", animated=True)

# For each new chunk read by the audio_callback function, we update the cursor position on the plot.
# It's important to notice that the audio_callback function CANNOT manipulate UI's widgets on it's
# own, because they live in different threads and Wx allows only the main thread to perform UI changes.
def OnPlaybackMove(self, event):
# =================================================
# Updates the cursor (vertical line) at each event:
# =================================================
self.figure.canvas.restore_region(self.background)
new_position = playback_counter*CHUNK_SIZE
self.playback_line.set_xdata(new_position)
self.axes1.draw_artist(self.playback_line)
self.canvas.blit(self.axes1.bbox)

# Playback move event (for indicating that a chunk has just been played and so the cursor must be moved):
EVT_PLAYBACK_MOVE = wx.PyEventBinder(wx.NewEventType(), 0)
class PlaybackMoveEvent(wx.PyCommandEvent):
def __init__(self, eventType=EVT_PLAYBACK_MOVE.evtType[0], id=0):
wx.PyCommandEvent.__init__(self, eventType, id)

# Callback function for audio playback (called each time the sound card needs "frame_count" more samples):
def audio_callback(in_data, frame_count, time_info, status):
global playback_counter
# In case we've run out of samples:
if playback_counter == len(audio_chunks):
print "Playback ended."
# Returns an empty chunk, thus ending playback:
return ("", pyaudio.paComplete)
else:
# Gets the next audio chunk, increments the counter and returns the new chunk:
new_chunk = audio_chunks[playback_counter]
main_window.AddPendingEvent(PlaybackMoveEvent())
playback_counter += 1
return (new_chunk, pyaudio.paContinue)

# WxGlade default initialization instructions:
if __name__ == "__main__":
gettext.install("app")
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
main_window = PlayerFrame(None, wx.ID_ANY, "")
app.SetTopWindow(main_window)
main_window.Show()
app.MainLoop() # UI's main loop. Checks for events and stuff.

# Final lines (if we're executing here, this means the program is closing):
audio_stream.close()
p.terminate()

非常感谢您的帮助和耐心等待!希望这不仅能帮助我,还能帮助其他与 WxPython WXAgg 后端 blitting 作斗争的人。

最佳答案

经过更多的研究,我终于发现解决方案是在从bbox有效复制之前调用canvas对象的draw()方法。因此,这里的中间线是答案(其他线仅作为放置修复的正确位置的引用):

    (...)
self.Layout()
self.figure.canvas.draw() # THIS is the solution.
self.background = self.figure.canvas.copy_from_bbox(self.axes1.bbox)

但我必须在这里补充一点,虽然这可能适用于某些情况,但您的绘图调整大小的任何情况都可能再次导致图像损坏。因此,为了解决这个问题,将您的方法绑定(bind)到图形 Canvas 的 "resize_event",并在您的方法内部强制重绘和新副本:

    self.playback_line = self.axes1.axvline(color="y", animated=True)

# New line here:
self.figure.canvas.mpl_connect("resize_event", self.on_resize_canvas)

# New method here:
def on_resize_canvas(self, event):
self.figure.canvas.draw()
self.background = self.figure.canvas.copy_from_bbox(self.axes1.bbox)

(...)

好了!这个问题占用了我项目的大量时间,所以我特意与其他人分享解决方案,因为这可能是 Internet 上第一个使用 WxPython、Matplotlib 和 PyAudio 提供的功能性音频播放器模板。希望你觉得它有用!

关于python - Matplotlib:音频播放器光标(垂直滑动线)的动画在 WxPython 上看起来很破损,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39523011/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com