2016-09-16 12 views
0

매우 특정한 목적으로 오디오 플레이어가 필요한 프로젝트를 작성 중입니다. 현재 WxPython, Matplotlib 및 PyAudio 패키지를 사용하고 있습니다. Matplotlib의 WXAgg 백엔드 (backend_wxagg)도 사용하고 있습니다.Matplotlib : WxPython에서 오디오 플레이어 커서 (수직선 슬라이딩) 애니메이션이 깨졌습니다.

기본적인 아이디어는 매우 간단하다 오디오 데이터는 메인 윈도우에 그려 동시에 플롯 재생하고 표시하는 동안 PyAudio 통해 재생한다 - 가로 슬라이딩 I는 애니메이션 수직선 할 의도 (을 커서), 예를 들어 Audacity에서 볼 수있는 유형입니다. 필자는 이미 일반적인 Matplotlib 애니메이션 예제를 시도해 보았지만 다른 일부는 웹을 통해 퍼져 나갔지 만 너무 느리거나 blitting되지 않고 FuncAnimation (프로젝트에 맞지 않는 아키텍처)에 의존하거나 기술 (내가 지금까지 작동하지 않는) 사용하려고합니다.

무언가가 실제로 화면에 이동하지만 검은 채워진 사각형 내 민트 노트북에 나타납니다 동안 전체 그림이 엉망 ... 흰색 채워진 사각형 내 우분투 (16) 바탕 화면에 플롯에 걸쳐 나타납니다 . 몇 일 동안 열심히 노력하고 ALMOST가 작동하도록 노력했지만, 겸손하게 도움을 청할 때가 왔습니다./

제가 아는 한 blit() 방법을 고집합니다. 그것은 (i) 맞춤 이벤트 (이 경우 오디오 프레임 소비)에서 플로팅을 새로 고침하고 (ii) 큰 성능의 가변 데이터 세트로 인해 좋은 성능을 보입니다.

# -*- 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() 

이 당신을 순전히 감사 : 최소한 아래로 내 프로젝트를 스트리핑

, 여기 한 번 돌보아 코드입니다, 잘하면 내 전체 응용 프로그램 (2000 + 선)을 고정 할 수 있습니다 너의 도움과 인내! 다행히도 이것은 나뿐 아니라 WxPython WXAgg 백엔드 블리 팅을 고민하는 다른 사람들에게 도움이되기를 바랍니다.

답변

0

더 많은 조사가 끝나면 마침내 해결책은 bbox에서 효과적으로 복사하기 전에 캔버스 객체 draw() 메서드를 호출하는 것이 었습니다. 따라서, 여기에 중간 선이 대답 (다른 사람이 수정 프로그램을 배치 할 올바른 자리에 참조 용으로 만 제공)입니다 :

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

는하지만 여기에 추가해야합니다 즉,이 경우 작동하는 반면, 플롯이 크기가 조정되는 시나리오에서는 이미지가 손상 될 수 있습니다. 그래서, 그 문제를 해결하기 위해, 그림 캔버스의 "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) 

(...) 

을 그리고 거기 당신은 간다! 이 문제는 많은 프로젝트 시간을 소비했기 때문에 WxPython, Matplotlib 및 PyAudio를 통해 인터넷에서 사용할 수있는 최초의 기능적 오디오 플레이어 템플릿 일지라도 다른 모든 사람들과 솔루션을 공유하는 것이 중요합니다. 희망 당신은 그것을 유용하게 찾을 수 있습니다!