0

I've been looking at threading and multithreading pages and tutorials all day and I still have no idea how to implement what I need. I have one class that reads an audio stream, and one that visualizes it with Tkinter, everybody's favorite package.

import numpy as np
from Tkinter import *
from cmath import exp,pi
import audioop,time,colorsys,pyaudio
import multiprocessing

class Recorder():
    def __init__(self):
        self.p=pyaudio.PyAudio()
        self.stream=self.p.open(format=pyaudio.paInt16,channels=1,rate=RATE,input=True,frames_per_buffer=CHUNK)
        self.buff = None
        self.rms = None
        self.m = None
        self.running = True
        return
    def run(self,q=None):
        while self.running:
            self.buff = np.fromstring(self.stream.read(CHUNK),dtype=np.int16)
            self.buff = np.fft.fft(self.buff)/CHUNK
            self.rms = audioop.rms(self.buff,2)
            self.buff = map(lambda y:(y.real**2+y.imag**2)**(0.5)/(1.516122797*self.rms),self.buff)[:len(self.buff)/2]
            self.m = float(max(self.buff))
    def get(self,q):
        q.put((self.buff,self.rms,self.m))
    def end(self):
        self.runing = False
        self.stream.stop_stream()
        self.stream.close()
        self.p.terminate()
class Drawer():
    def __init__(self):
        self.m = 0
        self.text = ''
    def draw(self,q):
        data,rms,m = q.get()
        if rms == 0: return False
        if m == 0: return False
        nData = list()
        i = 1
        while i*2 < len(data):
            nData.append(sum(data[i:i*2])/(i*2-1))
            i *= 2
        data = nData
        maxi = float(max(data))
        if maxi > self.m:
            self.m = maxi
        elif maxi <= 0.5*self.m:
            self.m *= 0.75
        bWidth = WIDTH/len(data)
        for x,y in enumerate(data):    
            canvas.delete("bar"+str(x))
            if GHEIGHT == tBarHeight:
                hexcode = "#%02x%02x%02x" % hsv2rgb(1-((y/self.m)*0.75),1,1)
                canvas.create_rectangle(x*(WIDTH/len(data)),HEIGHT,x*(WIDTH/len(data))+bWidth,HEIGHT-((y/self.m)*GHEIGHT),tag="bar"+str(x),fill=hexcode)
            else:
                drawBar(x,x*(WIDTH/len(data)),y,bWidth, self.m)
        canvas.delete('display')
        canvas.create_text(WIDTH/2, 20, text=self.text, tag = 'display')
        return True
def hsv2rgb(h,s,v):
    return tuple(int(i * 255) for i in colorsys.hsv_to_rgb(h,s,v))
def update():
    global times
    t1 = time.time()
##    recorder.run()
    if not drawer.draw(q):
        drawer.m = 0
    times.append(time.time()-t1)
    drawer.text = ("%.03f"%(sum(times)/len(times)))[2:]
    window.after(1,update)
def drawBar(sid, x, y, w, m):
    yVal = int(y/m*nBars)
    yN = HEIGHT
    h = GHEIGHT/nBars
    for bar in xrange(yVal):
        Hue = 1-(float(bar)/nBars)-0.3*1.1
        if Hue < 0: Hue = 0
        gradColor = '#%02x%02x%02x' % hsv2rgb(Hue,1,1)
        canvas.create_rectangle( x , HEIGHT-(h*bar), x + w, yN - h, fill = gradColor ,outline = "#FFFFFF", tag="bar"+str(sid))
        yN -= h
def click(event):
    global GHEIGHT
    if event.x <= WIDTH/2:
        GHEIGHT = window.winfo_screenheight()
    else:
        GHEIGHT = tBarHeight

global times
times = list()
CHUNK = 2048
RATE = 44100
WIDTH, HEIGHT = 1920, 1080
divs = CHUNK
mSec = 5
tBarHeight = 30
nBars = 20
window = Tk()
global GHEIGHT
GHEIGHT = window.winfo_screenheight()
window.attributes("-fullscreen",True)
canvas = Canvas(window, width=WIDTH, height=HEIGHT, bg="#FFFFFF")
canvas.bind(sequence = "<Button-1>",func = click)
canvas.pack()
#########
recorder = Recorder()
q = multiprocessing.Queue()
p = multiprocessing.Process(target=recorder.run, args=(q,))
p.start()
p.join()
drawer = Drawer()
#########
window.after(0,update)
window.mainloop()
recorder.end()

I know it's not the best code, and some parts of it are remnants of its evolution, but right now I want to be able to have recorder run its run function forever, and call draw() on it. The problem is that I don't know how to get run() to run forever and put the data into the queue. I have already received one pickle error on the put() line followed by an essay of traceback.

PicklingError: Can't pickle <type '_portaudio.Stream'>: it's not found as _portaudio.Stream

which traces back to

File "C:\Users\My Name\Documents\Programming\visualizer.py", line 108, in <module>
    p.start()
Neywiny
  • 81
  • 2
  • 9

1 Answers1

1

The issue is likely that either audioop or pyaudio is returning a _portaudio.Stream object which is unpickleable. Since multiple processes can't share memory, they have to pass messages back and forth using some serialization method, for which multiprocessing uses the pickle module. If you can't pickle an object, you can't pass it between processes.

Unless you need the performance benefits of using multiple CPU cores, you might be able to get away with using the threading module instead of multiprocessing.

whereswalden
  • 4,819
  • 3
  • 27
  • 41
  • See also http://stackoverflow.com/questions/7894791/use-numpy-array-in-shared-memory-for-multiprocessing and http://stackoverflow.com/questions/15414027/multiprocessing-pool-makes-numpy-matrix-multiplication-slower – torek Dec 26 '16 at 23:51
  • I added print statements to the recorder class, and run() and get() both don't get called. I also tried converting everything to python's native list, float and float to put into the tuple, but still it complains about the audiostream, even though that never gets put into the queue. As for threading, yeah I need the power since the visualizer is spending around 1/3 its time or so doing the calculations in run(), so I would like those done before I need them. – Neywiny Dec 27 '16 at 00:22
  • Ok I've moved all the pyaudio stuff right before the loop in run(), and get() gets called at the right time, but since run() isn't running for some reason, get() can't put() the values – Neywiny Dec 27 '16 at 00:43
  • @Neywiny I second the threading idea; this seems like a multithreading problem as opposed to a multiprocessing problem. – gowrath Dec 27 '16 at 02:42
  • If you guys say so, but after running the numbers, the recording and calculations take up 95% of the time spent, so I guess I'll have to balance it more but I'd prefer truly palatalized execution. – Neywiny Dec 27 '16 at 03:57