changeset 82:d288bc1174d4

audio input levels to graphite Ignore-this: b1422609f3bf51778b8b2284ca914916
author drewp@bigasterisk.com
date Fri, 02 Aug 2013 07:55:45 -0700
parents ef639d892e77
children add8a36e54bd
files service/audioInputLevels/audioInputLevelsAlsa.py service/audioInputLevels/audioInputLevelsPulse.py service/audioInputLevels/pydeps
diffstat 3 files changed, 175 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/audioInputLevels/audioInputLevelsAlsa.py	Fri Aug 02 07:55:45 2013 -0700
@@ -0,0 +1,59 @@
+from __future__ import division
+import argparse, alsaaudio, time, numpy, galena, socket
+
+def sendRecentAudio(accum, galenaOut, prefix):
+    samples = numpy.concatenate(accum)
+    samples = abs(samples / (1<<15))
+
+    galenaOut.send(prefix + '.avg', numpy.average(samples))
+    galenaOut.send(prefix + '.max', numpy.amax(samples))
+    
+def sendForever(card, prefix, periodSec, galenaOut):
+    inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, card)
+    inp.setchannels(1)
+    inp.setrate(44100)
+    inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
+    inp.setperiodsize(64)
+
+    readSleepSec = .05
+    lastSendTime = 0
+    accum = []
+    while True:
+
+        # I was getting machine hangs on an eeePC and I tried anything
+        # to make it not crash. I think this helped.
+        time.sleep(readSleepSec)
+
+        now = time.time()
+        if now - lastSendTime > periodSec * 2:
+            print "late: %s sec since last send and we have %s samples" % (
+                now - lastSendTime, sum(len(x) for x in accum))
+                
+        nframes, data = inp.read()
+        if nframes <= 0:
+            #print 'nframes', nframes, len(data)
+            continue # i get -32 a lot, don't know why
+        samples = numpy.fromstring(data, dtype=numpy.int16) 
+        accum.append(samples)
+
+        # -readSleepSec is in here to make sure we send a little too
+        # often (harmless) instead of missing a period sometimes,
+        # which makes a gap in the graph
+        if now > lastSendTime + periodSec - readSleepSec:
+            sendRecentAudio(accum, galenaOut, prefix)
+            lastSendTime = time.time()            
+
+            accum[:] = []
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+    '--card', required=True,
+    help='alsa card name (see list of unindented lines from `arecord -L`)')
+
+args = parser.parse_args()
+sendForever(
+    prefix='system.house.audio.%s' % socket.gethostname(),
+    periodSec=2,
+    card=args.card,
+    galenaOut=galena.Galena(host='bang'),
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/audioInputLevels/audioInputLevelsPulse.py	Fri Aug 02 07:55:45 2013 -0700
@@ -0,0 +1,114 @@
+# based on http://freshfoo.com/blog/pulseaudio_monitoring
+from __future__ import division
+import socket, argparse
+from Queue import Queue
+from ctypes import POINTER, c_ubyte, c_void_p, c_ulong, cast
+import galena
+
+# From https://github.com/Valodim/python-pulseaudio
+from pulseaudio import lib_pulseaudio as P
+
+METER_RATE = 1
+MAX_SAMPLE_VALUE = 127
+
+class PeakMonitor(object):
+
+    def __init__(self, source_name, rate):
+        self.source_name = source_name
+        self.rate = rate
+
+        # Wrap callback methods in appropriate ctypefunc instances so
+        # that the Pulseaudio C API can call them
+        self._context_notify_cb = P.pa_context_notify_cb_t(self.context_notify_cb)
+        self._source_info_cb = P.pa_source_info_cb_t(self.source_info_cb)
+        self._stream_read_cb = P.pa_stream_request_cb_t(self.stream_read_cb)
+
+        # stream_read_cb() puts peak samples into this Queue instance
+        self._samples = Queue()
+
+        # Create the mainloop thread and set our context_notify_cb
+        # method to be called when there's updates relating to the
+        # connection to Pulseaudio
+        _mainloop = P.pa_threaded_mainloop_new()
+        _mainloop_api = P.pa_threaded_mainloop_get_api(_mainloop)
+        context = P.pa_context_new(_mainloop_api, 'peak_demo')
+        P.pa_context_set_state_callback(context, self._context_notify_cb, None)
+        P.pa_context_connect(context, None, 0, None)
+        P.pa_threaded_mainloop_start(_mainloop)
+
+    def __iter__(self):
+        while True:
+            yield self._samples.get()
+
+    def context_notify_cb(self, context, _):
+        state = P.pa_context_get_state(context)
+
+        if state == P.PA_CONTEXT_READY:
+            print "Pulseaudio connection ready..."
+            # Connected to Pulseaudio. Now request that source_info_cb
+            # be called with information about the available sources.
+            o = P.pa_context_get_source_info_list(context, self._source_info_cb, None)
+            P.pa_operation_unref(o)
+
+        elif state == P.PA_CONTEXT_FAILED :
+            print "Connection failed"
+
+        elif state == P.PA_CONTEXT_TERMINATED:
+            print "Connection terminated"
+
+    def source_info_cb(self, context, source_info_p, _, __):
+        if not source_info_p:
+            return
+
+        source_info = source_info_p.contents
+
+        if source_info.name == self.source_name:
+            # Found the source we want to monitor for peak levels.
+            # Tell PA to call stream_read_cb with peak samples.
+            print 'setting up peak recording using', source_info.name
+            print 'description:', source_info.description
+            samplespec = P.pa_sample_spec()
+            samplespec.channels = 1
+            samplespec.format = P.PA_SAMPLE_U8
+            samplespec.rate = self.rate
+
+            pa_stream = P.pa_stream_new(context, "peak detect demo", samplespec, None)
+            P.pa_stream_set_read_callback(pa_stream,
+                                          self._stream_read_cb,
+                                          source_info.index)
+            P.pa_stream_connect_record(pa_stream,
+                                       source_info.name,
+                                       None,
+                                       P.PA_STREAM_PEAK_DETECT)
+
+    def stream_read_cb(self, stream, length, index_incr):
+        data = c_void_p()
+        P.pa_stream_peek(stream, data, c_ulong(length))
+        data = cast(data, POINTER(c_ubyte))
+        for i in xrange(length):
+            # When PA_SAMPLE_U8 is used, samples values range from 128
+            # to 255 because the underlying audio data is signed but
+            # it doesn't make sense to return signed peaks.
+            self._samples.put(data[i] - 128)
+        P.pa_stream_drop(stream)
+
+def main():
+
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--source', required=True,
+        help='pulseaudio source name (use `pactl list sources | grep Name`)')
+
+    args = parser.parse_args()
+
+    
+    out = galena.Galena(host='bang')
+    prefix = 'system.house.audio.%s' % socket.gethostname()
+    monitor = PeakMonitor(args.source, METER_RATE)
+    for sample in monitor:
+        #print ' %3d %s' % (sample, '>' * sample)
+        out.send(prefix + ".max", sample / 128)
+        
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/audioInputLevels/pydeps	Fri Aug 02 07:55:45 2013 -0700
@@ -0,0 +1,2 @@
+galena==0.3
+libpulseaudio==1.1