parallelise serial port usage and I/O for writing sounds files
--- a/scannr.py
+++ b/scannr.py
@@ -12,62 +12,104 @@
#python -m serial.tools.miniterm -p COM20 -e -b 115200 --cr
import psycopg2
import csv
-import sys,os
+import sys
+import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__) or '.', 'pynma'))
import pynma
filename = "demo.wav"
+last_call = (None, None, None)
MIN_LENGTH = 90000
+lock = threading.RLock()
-def worker(filename, length):
+def get_call():
+ global lock
+ with lock:
+ ser.write("GLG\r")
+ line = ser.readline() # read a '\n' terminated line
+ print line
+ reader = csv.reader([line])
+ for row in reader:
+ #GLG,40078,NFM,0,0,CanberraBlackMnt,AustralianCapita,SES Ops 1,1,0
+ #,NONE,NONE,NONE
+ if (row[0] != 'GLG' or row[1] == ''):
+ print "uh oh"
+ return (None, None, None)
+ if (row[1] != ''):
+ tgid = row[1]
+ #nma.push("scannr", "ping", filename, "http://www.google.com")
+ tgname = row[7]
+ sitename = row[5]
+ return (tgid, tgname, sitename)
+
+def log_recording(tgid, tgname, sitename, filename, length):
+ cur = conn.cursor()
+ cur.execute("INSERT INTO recordings \
+ (tgid, tgname, sitename, filename, length)\
+ VALUES (%s, %s, %s, %s, %s)",( tgid, tgname, sitename, filename, length))
+ conn.commit()
+ cur.close()
+
+
+def tgid_worker():
+ global last_call
+ last_call = get_call()
+
+
+def save_worker(filename, length):
+ global last_call
"""thread worker function
http://www.doughellmann.com/PyMOTW/threading/
https://github.com/uskr/pynma
ffmpeg -i 2012-09-29-1348911268.34-demo.wav -ar 8000 -ab 4.75k test.3gp
-http://stackoverflow.com/questions/2559746/getting-error-while-converting-wav-to-amr-using-ffmpeg
+http://stackoverflow.com/questions/2559746/getting-error-while-converting-
+wav-to-amr-using-ffmpeg
"""
- print 'Worker for '+filename
- ser.write("GLG\r")
- line = ser.readline() # read a '\n' terminated line
- print line
- reader = csv.reader([line])
- for row in reader:
- #GLG,40078,NFM,0,0,CanberraBlackMnt,AustralianCapita,SES Ops 1,1,0,NONE,NONE,NONE
- if (row[0] != 'GLG'):
- print "uh oh"
- if (row[1] != ''):
- tgid = row[1]
- #nma.push("scannr", "ping", filename, "http://www.google.com")
- tgname = row[7]
- sitename = row[5]
- """http://initd.org/psycopg/docs/usage.html"""
- cur = conn.cursor()
- cur.execute("INSERT INTO recordings (filename,tgid,tgname,sitename,length) VALUES (%s, %s,%s, %s, %s)",(filename,tgid,tgname,sitename, length))
- conn.commit()
- cur.close()
+ print 'Worker for ' + filename
+ (oldtgid, oldtgname, oldsitename) = last_call
+ (tgid, tgname, sitename) = get_call()
+ if oldtgid == tgid:
+ if tgid == None or tgid == '':
+ print filename + " has no TGID"
else:
- print filename+" has no TGID"
+ log_recording(tgid, tgname, sitename, filename, length)
+ else:
+ if tgid == None or tgid == '':
+ print filename + " has no TGID"
+ else:
+ log_recording(tgid, tgname, sitename, filename, length)
+ if oldtgid == None or oldtgid == '':
+ print filename + " has no old TGID"
+ else:
+ log_recording(oldtgid, oldtgname, oldsitename, filename, length)
+ return
- return
def filenameMaker():
global filename
filename = date.today().isoformat()+'-'+str(time.time())+'-demo.wav'
-def record_to_async_file():
- "Records from the microphone and outputs the resulting data to `path`"
+def do_record():
sample_width, data = snd.record()
- print str(len(data))
+ thr = threading.Thread(target=record_to_async_file, args=(sample_width,data,filename))
+ thr.start()
+
+
+def record_to_async_file(sample_width,data,filename):
+ "Records from the microphone and outputs the resulting data to `filename`"
+ print "Recording complete"
if len(data) > MIN_LENGTH:
+ print "Recording being saved..."
+ dispatcher.send( signal='FILE_CREATED', sender=filename, filename=filename, length=len(data))
+ print str(len(data))
data = snd.pack('<' + ('h'*len(data)), *data)
path = "./data/"+filename
- dispatcher.send( signal='FILE_CREATED', sender=filename, filename=filename, length=len(data))
wf = wave.open(path, 'wb')
- wf.setnchannels(1)
+ wf.setnchannels(2)
wf.setsampwidth(sample_width)
wf.setframerate(snd.RATE)
wf.writeframes(data)
@@ -77,20 +119,23 @@
del data
dispatcher.connect( filenameMaker, signal='SND_STARTED', sender=dispatcher.Any )
-dispatcher.connect( worker, signal='FILE_CREATED', sender=dispatcher.Any )
+dispatcher.connect( tgid_worker, signal='SND_STARTED', sender=dispatcher.Any )
+dispatcher.connect( save_worker, signal='FILE_CREATED', sender=dispatcher.Any )
print "Opening serial port..."
if sys.platform.startswith('darwin'):
- ser = serial.Serial('/dev/tty.usbserial-FTB3VL83', 112500, timeout=1)
+ ser = serial.Serial('/dev/tty.usbserial-FTB3VL83', 112500, timeout=1)
elif sys.platform.startswith('win32'):
- ser = serial.Serial('COM20', 112500, timeout=1)
+ ser = serial.Serial('COM20', 112500, timeout=1)
print "Loading notifymyandroid..."
-nma = pynma.PyNMA( "a6f50f76119eda33befe4325b4b9e1dd25eef7bad2868e4f")
+nma = pynma.PyNMA( "a6f50f76119eda33befe4325b4b9e1dd25eef7bad2868e4f")
print "Connecting database..."
conn = psycopg2.connect("dbname=scannr user=postgres password=snmc")
print "Scannr started."
while True:
print "ready to record again"
- record_to_async_file()
+ do_record()
ser.close()
+
+
--- a/snd.py
+++ b/snd.py
@@ -1,15 +1,12 @@
-""" Record a few seconds of audio and save to a WAVE file.
+""" Record a few seconds of audio and save to a WAVE file.
Based on http://stackoverflow.com/questions/892199/detect-record-audio-in-python/6743593#6743593
"""
import pyaudio
import wave
import sys
-import audioop # http://docs.python.org/library/audioop
-from os.path import exists
from array import array
from struct import unpack, pack
-import threading
from pydispatch import dispatcher
THRESHOLD = 500
@@ -17,11 +14,11 @@
FORMAT = pyaudio.paInt16
RATE = 44100
if sys.platform.startswith('darwin'):
- CHANNELS = 2
+ CHANNELS = 2
elif sys.platform.startswith('win32'):
- CHANNELS = 1
+ CHANNELS = 1
-MAX_SILENT = 30
+MAX_SILENT = 80
def is_silent(L):
"Returns `True` if below the 'silent' threshold"
@@ -72,16 +69,16 @@
def record():
"""
- Record a word or words from the microphone and
+ Record a word or words from the microphone and
return the data as an array of signed shorts.
- Normalizes the audio, trims silence from the
- start and end, and pads with 0.5 seconds of
- blank sound to make sure VLC et al can play
+ Normalizes the audio, trims silence from the
+ start and end, and pads with 0.5 seconds of
+ blank sound to make sure VLC et al can play
it without getting chopped off.
"""
p = pyaudio.PyAudio()
- stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE,
+ stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE,
input=True,
frames_per_buffer=CHUNK_SIZE)
@@ -91,7 +88,12 @@
LRtn = array('h')
while 1:
- data = stream.read(CHUNK_SIZE)
+ try:
+ data = stream.read(CHUNK_SIZE)
+ except IOError as ex:
+ if ex[1] != pyaudio.paInputOverflowed:
+ raise
+ data = '\x00' * CHUNK_SIZE
L = unpack('<' + ('h'*(len(data)/2)), data) # little endian, signed short
L = array('h', L)
@@ -100,11 +102,13 @@
if silent and snd_started:
num_silent += 1
- print num_silent
+ #print num_silent
elif not silent and not snd_started:
dispatcher.send( signal='SND_STARTED')
snd_started = True
print snd_started
+ if snd_started and not silent:
+ num_silent = 0
if snd_started:
LRtn.extend(L)
if snd_started and num_silent > MAX_SILENT:
@@ -126,7 +130,7 @@
data = pack('<' + ('h'*len(data)), *data)
wf = wave.open(path, 'wb')
- wf.setnchannels(1)
+ wf.setnchannels(CHANNELS)
wf.setsampwidth(sample_width)
wf.setframerate(RATE)
wf.writeframes(data)
--- /dev/null
+++ b/test.py
@@ -1,1 +1,41 @@
+"""PyAudio example: Record a few seconds of audio and save to a WAVE file."""
+import pyaudio
+import wave
+
+CHUNK = 1024
+FORMAT = pyaudio.paInt16
+CHANNELS = 2
+RATE = 44100
+RECORD_SECONDS = 5
+WAVE_OUTPUT_FILENAME = "output.wav"
+
+p = pyaudio.PyAudio()
+
+stream = p.open(format=FORMAT,
+ channels=CHANNELS,
+ rate=RATE,
+ input=True,
+ frames_per_buffer=CHUNK)
+
+print("* recording")
+
+frames = []
+
+for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
+ data = stream.read(CHUNK)
+ frames.append(data)
+
+print("* done recording")
+
+stream.stop_stream()
+stream.close()
+p.terminate()
+
+wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
+wf.setnchannels(CHANNELS)
+wf.setsampwidth(p.get_sample_size(FORMAT))
+wf.setframerate(RATE)
+wf.writeframes(b''.join(frames))
+wf.close()
+
--- /dev/null
+++ b/test2.py
@@ -1,1 +1,121 @@
+from os.path import exists
+from array import array
+from struct import unpack, pack
+import pyaudio
+import wave
+
+THRESHOLD = 500
+CHUNK_SIZE = 1024
+FORMAT = pyaudio.paInt16
+RATE = 44100
+
+def is_silent(L):
+ "Returns `True` if below the 'silent' threshold"
+ return max(L) < THRESHOLD
+
+def normalize(L):
+ "Average the volume out"
+ MAXIMUM = 16384
+ times = float(MAXIMUM)/max(abs(i) for i in L)
+
+ LRtn = array('h')
+ for i in L:
+ LRtn.append(int(i*times))
+ return LRtn
+
+def trim(L):
+ "Trim the blank spots at the start and end"
+ def _trim(L):
+ snd_started = False
+ LRtn = array('h')
+
+ for i in L:
+ if not snd_started and abs(i)>THRESHOLD:
+ snd_started = True
+ LRtn.append(i)
+
+ elif snd_started:
+ LRtn.append(i)
+ return LRtn
+
+ # Trim to the left
+ L = _trim(L)
+
+ # Trim to the right
+ L.reverse()
+ L = _trim(L)
+ L.reverse()
+ return L
+
+def add_silence(L, seconds):
+ "Add silence to the start and end of `L` of length `seconds` (float)"
+ LRtn = array('h', [0 for i in xrange(int(seconds*RATE))])
+ LRtn.extend(L)
+ LRtn.extend([0 for i in xrange(int(seconds*RATE))])
+ return LRtn
+
+def record():
+ """
+ Record a word or words from the microphone and
+ return the data as an array of signed shorts.
+
+ Normalizes the audio, trims silence from the
+ start and end, and pads with 0.5 seconds of
+ blank sound to make sure VLC et al can play
+ it without getting chopped off.
+ """
+ p = pyaudio.PyAudio()
+ stream = p.open(format=FORMAT, channels=2, rate=RATE,
+ input=True,
+ frames_per_buffer=CHUNK_SIZE)
+
+ num_silent = 0
+ snd_started = False
+
+ LRtn = array('h')
+
+ while 1:
+ data = stream.read(CHUNK_SIZE)
+ L = unpack('<' + ('h'*(len(data)/2)), data) # little endian, signed short
+ L = array('h', L)
+ LRtn.extend(L)
+
+ silent = is_silent(L)
+ #print silent, num_silent, L[:10]
+
+ if silent and snd_started:
+ num_silent += 1
+ elif not silent and not snd_started:
+ snd_started = True
+
+ if snd_started and num_silent > 30:
+ break
+
+ sample_width = p.get_sample_size(FORMAT)
+ stream.stop_stream()
+ stream.close()
+ p.terminate()
+
+ LRtn = normalize(LRtn)
+ LRtn = trim(LRtn)
+ LRtn = add_silence(LRtn, 0.5)
+ return sample_width, LRtn
+
+def record_to_file(path):
+ "Records from the microphone and outputs the resulting data to `path`"
+ sample_width, data = record()
+ data = pack('<' + ('h'*len(data)), *data)
+
+ wf = wave.open(path, 'wb')
+ wf.setnchannels(2)
+ wf.setsampwidth(sample_width)
+ wf.setframerate(RATE)
+ wf.writeframes(data)
+ wf.close()
+
+if __name__ == '__main__':
+ print("please speak a word into the microphone")
+ record_to_file('demo.wav')
+ print("done - result written to demo.wav")
+