Mercurial > code > home > repos > homeauto
comparison service/audioInputLevels/audioInputLevelsPulse.py @ 1144:b9981f50b82d
audioInputLevels robustness and cleanup
Ignore-this: f7bf0ef344cc704ed15d5d7f330e235e
darcs-hash:1699712f056d62957ff365b9f6051ca6ddc54eca
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Sat, 03 Mar 2018 18:12:18 -0800 |
parents | 03b4882517dd |
children | c1d38b884a2e |
comparison
equal
deleted
inserted
replaced
1143:d1bc88f67969 | 1144:b9981f50b82d |
---|---|
1 # based on http://freshfoo.com/blog/pulseaudio_monitoring | 1 # based on http://freshfoo.com/blog/pulseaudio_monitoring |
2 from __future__ import division | 2 from __future__ import division |
3 import socket, argparse, time | 3 import socket, argparse, time, logging, os |
4 from Queue import Queue | 4 from Queue import Queue |
5 from ctypes import POINTER, c_ubyte, c_void_p, c_ulong, cast | 5 from ctypes import POINTER, c_ubyte, c_void_p, c_ulong, cast |
6 from influxdb import InfluxDBClient | 6 from influxdb import InfluxDBClient |
7 | 7 |
8 # From https://github.com/Valodim/python-pulseaudio | 8 # From https://github.com/Valodim/python-pulseaudio |
9 from pulseaudio import lib_pulseaudio as P | 9 from pulseaudio import lib_pulseaudio as P |
10 | 10 |
11 logging.basicConfig(level=logging.INFO) | |
12 log = logging.getLogger() | |
13 | |
11 METER_RATE = 1 | 14 METER_RATE = 1 |
12 MAX_SAMPLE_VALUE = 127 | |
13 | 15 |
14 class PeakMonitor(object): | 16 class PeakMonitor(object): |
15 | 17 |
16 def __init__(self, source_name, rate): | 18 def __init__(self, source_name, rate): |
17 self.source_name = source_name | 19 self.source_name = source_name |
42 | 44 |
43 def context_notify_cb(self, context, _): | 45 def context_notify_cb(self, context, _): |
44 state = P.pa_context_get_state(context) | 46 state = P.pa_context_get_state(context) |
45 | 47 |
46 if state == P.PA_CONTEXT_READY: | 48 if state == P.PA_CONTEXT_READY: |
47 print "Pulseaudio connection ready..." | 49 log.info("Pulseaudio connection ready...") |
48 # Connected to Pulseaudio. Now request that source_info_cb | 50 # Connected to Pulseaudio. Now request that source_info_cb |
49 # be called with information about the available sources. | 51 # be called with information about the available sources. |
50 o = P.pa_context_get_source_info_list(context, self._source_info_cb, None) | 52 o = P.pa_context_get_source_info_list(context, self._source_info_cb, None) |
51 P.pa_operation_unref(o) | 53 P.pa_operation_unref(o) |
52 | 54 |
53 elif state == P.PA_CONTEXT_FAILED : | 55 elif state == P.PA_CONTEXT_FAILED : |
54 print "Connection failed" | 56 log.error("Connection failed") |
57 os.abort() | |
55 | 58 |
56 elif state == P.PA_CONTEXT_TERMINATED: | 59 elif state == P.PA_CONTEXT_TERMINATED: |
57 print "Connection terminated" | 60 log.error("Connection terminated") |
61 os.abort() | |
62 | |
63 else: | |
64 log.info('context_notify_cb state=%r', state) | |
58 | 65 |
59 def source_info_cb(self, context, source_info_p, _, __): | 66 def source_info_cb(self, context, source_info_p, _, __): |
60 if not source_info_p: | 67 if not source_info_p: |
61 return | 68 return |
62 | 69 |
63 source_info = source_info_p.contents | 70 source_info = source_info_p.contents |
64 | 71 |
65 if source_info.name == self.source_name: | 72 if source_info.name == self.source_name: |
66 # Found the source we want to monitor for peak levels. | 73 # Found the source we want to monitor for peak levels. |
67 # Tell PA to call stream_read_cb with peak samples. | 74 # Tell PA to call stream_read_cb with peak samples. |
68 print 'setting up peak recording using', source_info.name | 75 log.info('setting up peak recording using %s', source_info.name) |
69 print 'description:', source_info.description | 76 log.info('description: %r', source_info.description) |
77 | |
70 samplespec = P.pa_sample_spec() | 78 samplespec = P.pa_sample_spec() |
71 samplespec.channels = 1 | 79 samplespec.channels = 1 |
72 samplespec.format = P.PA_SAMPLE_U8 | 80 samplespec.format = P.PA_SAMPLE_U8 |
73 samplespec.rate = self.rate | 81 samplespec.rate = self.rate |
74 | 82 pa_stream = P.pa_stream_new(context, "audioInputLevels", samplespec, None) |
75 pa_stream = P.pa_stream_new(context, "peak detect demo", samplespec, None) | 83 |
76 P.pa_stream_set_read_callback(pa_stream, | 84 P.pa_stream_set_read_callback(pa_stream, |
77 self._stream_read_cb, | 85 self._stream_read_cb, |
78 source_info.index) | 86 source_info.index) |
79 P.pa_stream_connect_record(pa_stream, | 87 P.pa_stream_connect_record(pa_stream, |
80 source_info.name, | 88 source_info.name, |
83 | 91 |
84 def stream_read_cb(self, stream, length, index_incr): | 92 def stream_read_cb(self, stream, length, index_incr): |
85 data = c_void_p() | 93 data = c_void_p() |
86 P.pa_stream_peek(stream, data, c_ulong(length)) | 94 P.pa_stream_peek(stream, data, c_ulong(length)) |
87 data = cast(data, POINTER(c_ubyte)) | 95 data = cast(data, POINTER(c_ubyte)) |
88 for i in xrange(length): | 96 try: |
89 # When PA_SAMPLE_U8 is used, samples values range from 128 | 97 for i in xrange(length): |
90 # to 255 because the underlying audio data is signed but | 98 # When PA_SAMPLE_U8 is used, samples values range from 128 |
91 # it doesn't make sense to return signed peaks. | 99 # to 255 because the underlying audio data is signed but |
92 self._samples.put(data[i] - 128) | 100 # it doesn't make sense to return signed peaks. |
101 self._samples.put(data[i] - 128) | |
102 except ValueError: | |
103 # "data will be NULL and nbytes will contain the length of the hole" | |
104 log.info("skipping hole of length %s" % length) | |
105 # This seems to happen at startup for a while. | |
93 P.pa_stream_drop(stream) | 106 P.pa_stream_drop(stream) |
94 | 107 |
95 def main(): | 108 def main(): |
96 | |
97 | |
98 parser = argparse.ArgumentParser() | 109 parser = argparse.ArgumentParser() |
99 parser.add_argument( | 110 parser.add_argument( |
100 '--source', required=True, | 111 '--source', required=True, |
101 help='pulseaudio source name (use `pactl list sources | grep Name`)') | 112 help='pulseaudio source name (use `pactl list sources | grep Name`)') |
102 | 113 |
103 args = parser.parse_args() | 114 args = parser.parse_args() |
104 | 115 |
105 def ipv6Init(self, host="localhost", port=2003): | |
106 self._addr = (host, port, 0, 0) | |
107 self._sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) | |
108 self._sock.connect(self._addr) | |
109 | |
110 influx = InfluxDBClient('bang6', 9060, 'root', 'root', 'main') | 116 influx = InfluxDBClient('bang6', 9060, 'root', 'root', 'main') |
111 | 117 |
112 hostname = socket.gethostname() | 118 hostname = socket.gethostname() |
113 monitor = PeakMonitor(args.source, METER_RATE) | 119 monitor = PeakMonitor(args.source, METER_RATE) |
114 for sample in monitor: | 120 for sample in monitor: |
115 #print ' %3d %s' % (sample, '>' * sample) | 121 log.debug(' %3d %s', sample, '>' * sample) |
116 influx.write_points([{'measurement': 'audioLevel', | 122 influx.write_points([{'measurement': 'audioLevel', |
117 "tags": dict(stat='max', location=hostname), | 123 "tags": dict(stat='max', location=hostname), |
118 "fields": {"value": sample / 128}, | 124 "fields": {"value": sample / 128}, |
119 "time": int(time.time())}], time_precision='s') | 125 "time": int(time.time())}], time_precision='s') |
120 | 126 |
121 if __name__ == '__main__': | 127 if __name__ == '__main__': |
122 main() | 128 main() |