Mercurial > code > home > repos > homeauto
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()