[Twisted-Python] PyMedia & Tk - based mp3 sound recorder
![](https://secure.gravatar.com/avatar/0412034499afbb7563aa7c6b2be69fc3.jpg?s=120&d=mm&r=g)
I've tried to write a simple sound recorder for python. Basically, it just reads sound from mic or line in, and encodes it to mp3 on the fly. See http://pymedia.org/tut/src/voice_recorder.py.html for a simple example I've taken the pymedia recorder code from. Works nicely - but no GUI etc. of course. I've added a very simple Tk-base GUI using twisted, using task.LoopingCall to drive the recorder loop that reads chunks data from sound device and encodes them. The problem is that the sound is now garbled - it sounds as if the loop is not fast enough or it's dropping some sound data. Apart from twisted and Tk integration, the code should be essentially the same as the original example that works fine. I''ve tried with different task looping frequencies and even added Psyco optiomization to the code, and all this has made but tiny difference -and my PC is an AMD 2400+ with plenty of memory so I would think speed is not the issue here; the encoding preferences are mono, 22050Hz and 64kbit/s. The source code (about 120 lines in all) is attached - any advice or improvement suggestions would be greatly appreciated. Thanks, Petri import time, sys, urlparse, tkFileDialog import Tkinter as Tk import pymedia.audio.sound as sound import pymedia.audio.acodec as acodec from twisted.internet import task from twisted.internet import reactor, tksupport # constants STOPPED = 0 RECORDING = 1 # input params insrc = "VIA AC'97 Audio (WAVE)" format = sound.AFMT_U16_LE # encoder parameters params = { 'id': acodec.getCodecId("mp3"), 'bitrate': 64000, 'sample_rate': 22050, 'channels': 1 } class Recorder: def __init__(self, params=params): self.task = task.LoopingCall(self.process) self.status = STOPPED self.encoder= acodec.Encoder(params) # determine input source inputid=None for d in sound.getIDevices(): if d['name'] == insrc: inputid = d['id'] if not inputid: raise Exception("Invalid or no input source") self.snd= sound.Input(22050, 1, format, inputid) def process(self): #while 1: # snd.getPosition()<= secs: s= self.snd.getData() if s and len(s): for fr in self.encoder.encode(s): # We definitely should use mux first, but for # simplicity reasons this way it'll work also self.outfile.write(fr) #else: # time.sleep( .001 ) def start(self, file): self.outfile= file self.snd.start() self.task.start(.0000001) def stop(self): # Stop listening the incoming sound from the microphone or line in self.task.stop() self.snd.stop() self.outfile.close() class RecorderGUI: def __init__(self, root): frame = Tk.Frame(root) self.frame=frame self.startB = Tk.Button(frame, padx=20, pady=2, text="Start", command=self.startRecording) self.stopB = Tk.Button(frame, padx=20, pady=2, text="Stop", command=self.stopRecording, state=Tk.DISABLED) self.quitB = Tk.Button(frame, padx=20, pady=2, text="Quit", command=self.quitApplication) self.startB.pack(side=Tk.LEFT) self.stopB.pack(side=Tk.LEFT) self.quitB.pack(side=Tk.RIGHT) frame.pack() def setRecorder(self,recorder): self.recorder = recorder def startRecording(self): file = tkFileDialog.asksaveasfile(title="Enter file to save to") self.stopB['state'] = Tk.NORMAL self.startB['state'] =Tk.DISABLED self.recorder.start(file) def stopRecording(self): self.recorder.stop() self.stopB['state'] = Tk.DISABLED self.startB['state'] = Tk.NORMAL def quitApplication(self): if self.recorder.status == RECORDING: self.recorder.stop() reactor.stop() def run(): import psyco psyco.full() guiroot = Tk.Tk() guiroot.protocol("WM_DELETE_WINDOW", reactor.stop) guiroot.title("Sound recorder") tksupport.install(guiroot) gui=RecorderGUI(guiroot) gui.recorder = Recorder() reactor.run() if __name__ == '__main__': run()
![](https://secure.gravatar.com/avatar/d7875f8cfd8ba9262bfff2bf6f6f9b35.jpg?s=120&d=mm&r=g)
On Mon, 2004-11-29 at 16:55 +0200, Petri Savolainen wrote:
LoopingCall has a max resolution dependent on the clock resolution. E.g. on Linux 2.6 LoopingCall is good up to 1ms resolution (0.001 seconds), on 2.4 by default 10ms and 2ms on redhat boxes, but that's about it.
![](https://secure.gravatar.com/avatar/0412034499afbb7563aa7c6b2be69fc3.jpg?s=120&d=mm&r=g)
Itamar Shtull-Trauring wrote:
Good to know - I was wondering what's the maximum. I've tried quite a few different values for the resolution, ranging from 0.05 to about 0.00001 or so, with no significant variation to audio quality. Petri
![](https://secure.gravatar.com/avatar/7ed9784cbb1ba1ef75454034b3a8e6a1.jpg?s=120&d=mm&r=g)
On Mon, 29 Nov 2004 16:55:42 +0200, Petri Savolainen <petri.savolainen@iki.fi> wrote:
You may want to look at how Shtoom gets its audio input. It uses Twisted and seems to have audio handling down pretty well. http://www.divmod.org/Home/Projects/Shtoom/ Jp
![](https://secure.gravatar.com/avatar/0412034499afbb7563aa7c6b2be69fc3.jpg?s=120&d=mm&r=g)
Thanks for everyone who responded. Embarrassingly, I had forgotten to make sure the file was opened in binary mode...
def startRecording(self): file = tkFileDialog.asksaveasfile(title="Enter file to save to")
So instead: file = tkFileDialog.asksaveasfile(mode="wb", title="Enter file to save to")
![](https://secure.gravatar.com/avatar/d7875f8cfd8ba9262bfff2bf6f6f9b35.jpg?s=120&d=mm&r=g)
On Mon, 2004-11-29 at 16:55 +0200, Petri Savolainen wrote:
LoopingCall has a max resolution dependent on the clock resolution. E.g. on Linux 2.6 LoopingCall is good up to 1ms resolution (0.001 seconds), on 2.4 by default 10ms and 2ms on redhat boxes, but that's about it.
![](https://secure.gravatar.com/avatar/0412034499afbb7563aa7c6b2be69fc3.jpg?s=120&d=mm&r=g)
Itamar Shtull-Trauring wrote:
Good to know - I was wondering what's the maximum. I've tried quite a few different values for the resolution, ranging from 0.05 to about 0.00001 or so, with no significant variation to audio quality. Petri
![](https://secure.gravatar.com/avatar/7ed9784cbb1ba1ef75454034b3a8e6a1.jpg?s=120&d=mm&r=g)
On Mon, 29 Nov 2004 16:55:42 +0200, Petri Savolainen <petri.savolainen@iki.fi> wrote:
You may want to look at how Shtoom gets its audio input. It uses Twisted and seems to have audio handling down pretty well. http://www.divmod.org/Home/Projects/Shtoom/ Jp
![](https://secure.gravatar.com/avatar/0412034499afbb7563aa7c6b2be69fc3.jpg?s=120&d=mm&r=g)
Thanks for everyone who responded. Embarrassingly, I had forgotten to make sure the file was opened in binary mode...
def startRecording(self): file = tkFileDialog.asksaveasfile(title="Enter file to save to")
So instead: file = tkFileDialog.asksaveasfile(mode="wb", title="Enter file to save to")
participants (4)
-
Anthony Baxter
-
Itamar Shtull-Trauring
-
Jp Calderone
-
Petri Savolainen