Mercurial > code > home > repos > homeauto
annotate service/audioInputLevels/audioInputLevelsPulse.py @ 1161:6139847a72d4
switch to docopt, add -v
Ignore-this: 1fbc97d2ead1396652d9af9adf5637b
darcs-hash:115eb637fc7b82ca7a1ac0d0f4883aa066a728cb
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Wed, 05 Sep 2018 01:51:14 -0700 |
parents | b9981f50b82d |
children | b087642a456f |
rev | line source |
---|---|
887 | 1 # based on http://freshfoo.com/blog/pulseaudio_monitoring |
2 from __future__ import division | |
1161 | 3 import socket, time, logging, os |
887 | 4 from Queue import Queue |
5 from ctypes import POINTER, c_ubyte, c_void_p, c_ulong, cast | |
1161 | 6 from docopt import docopt |
1112
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
7 from influxdb import InfluxDBClient |
887 | 8 |
9 # From https://github.com/Valodim/python-pulseaudio | |
10 from pulseaudio import lib_pulseaudio as P | |
11 | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
12 logging.basicConfig(level=logging.INFO) |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
13 log = logging.getLogger() |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
14 |
887 | 15 METER_RATE = 1 |
16 | |
17 class PeakMonitor(object): | |
18 | |
19 def __init__(self, source_name, rate): | |
20 self.source_name = source_name | |
21 self.rate = rate | |
22 | |
23 # Wrap callback methods in appropriate ctypefunc instances so | |
24 # that the Pulseaudio C API can call them | |
25 self._context_notify_cb = P.pa_context_notify_cb_t(self.context_notify_cb) | |
26 self._source_info_cb = P.pa_source_info_cb_t(self.source_info_cb) | |
27 self._stream_read_cb = P.pa_stream_request_cb_t(self.stream_read_cb) | |
28 | |
29 # stream_read_cb() puts peak samples into this Queue instance | |
30 self._samples = Queue() | |
31 | |
32 # Create the mainloop thread and set our context_notify_cb | |
33 # method to be called when there's updates relating to the | |
34 # connection to Pulseaudio | |
35 _mainloop = P.pa_threaded_mainloop_new() | |
36 _mainloop_api = P.pa_threaded_mainloop_get_api(_mainloop) | |
37 context = P.pa_context_new(_mainloop_api, 'peak_demo') | |
38 P.pa_context_set_state_callback(context, self._context_notify_cb, None) | |
39 P.pa_context_connect(context, None, 0, None) | |
40 P.pa_threaded_mainloop_start(_mainloop) | |
41 | |
42 def __iter__(self): | |
43 while True: | |
44 yield self._samples.get() | |
45 | |
46 def context_notify_cb(self, context, _): | |
47 state = P.pa_context_get_state(context) | |
48 | |
49 if state == P.PA_CONTEXT_READY: | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
50 log.info("Pulseaudio connection ready...") |
887 | 51 # Connected to Pulseaudio. Now request that source_info_cb |
52 # be called with information about the available sources. | |
53 o = P.pa_context_get_source_info_list(context, self._source_info_cb, None) | |
54 P.pa_operation_unref(o) | |
55 | |
56 elif state == P.PA_CONTEXT_FAILED : | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
57 log.error("Connection failed") |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
58 os.abort() |
887 | 59 |
60 elif state == P.PA_CONTEXT_TERMINATED: | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
61 log.error("Connection terminated") |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
62 os.abort() |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
63 |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
64 else: |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
65 log.info('context_notify_cb state=%r', state) |
887 | 66 |
67 def source_info_cb(self, context, source_info_p, _, __): | |
68 if not source_info_p: | |
69 return | |
70 | |
71 source_info = source_info_p.contents | |
72 | |
73 if source_info.name == self.source_name: | |
74 # Found the source we want to monitor for peak levels. | |
75 # Tell PA to call stream_read_cb with peak samples. | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
76 log.info('setting up peak recording using %s', source_info.name) |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
77 log.info('description: %r', source_info.description) |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
78 |
887 | 79 samplespec = P.pa_sample_spec() |
80 samplespec.channels = 1 | |
81 samplespec.format = P.PA_SAMPLE_U8 | |
82 samplespec.rate = self.rate | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
83 pa_stream = P.pa_stream_new(context, "audioInputLevels", samplespec, None) |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
84 |
887 | 85 P.pa_stream_set_read_callback(pa_stream, |
86 self._stream_read_cb, | |
87 source_info.index) | |
88 P.pa_stream_connect_record(pa_stream, | |
89 source_info.name, | |
90 None, | |
91 P.PA_STREAM_PEAK_DETECT) | |
92 | |
93 def stream_read_cb(self, stream, length, index_incr): | |
94 data = c_void_p() | |
95 P.pa_stream_peek(stream, data, c_ulong(length)) | |
96 data = cast(data, POINTER(c_ubyte)) | |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
97 try: |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
98 for i in xrange(length): |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
99 # When PA_SAMPLE_U8 is used, samples values range from 128 |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
100 # to 255 because the underlying audio data is signed but |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
101 # it doesn't make sense to return signed peaks. |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
102 self._samples.put(data[i] - 128) |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
103 except ValueError: |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
104 # "data will be NULL and nbytes will contain the length of the hole" |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
105 log.info("skipping hole of length %s" % length) |
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
106 # This seems to happen at startup for a while. |
887 | 107 P.pa_stream_drop(stream) |
108 | |
109 def main(): | |
1161 | 110 arg = docopt(""" |
111 Usage: audioInputLevelsPulse.py [-v] --source=<name> | |
887 | 112 |
1161 | 113 --source=<name> pulseaudio source name (use `pactl list sources | grep Name`) |
114 -v Verbose | |
115 """) | |
116 | |
117 log.setLevel(logging.DEBUG if arg['-v'] else logging.INFO) | |
887 | 118 |
1112
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
119 influx = InfluxDBClient('bang6', 9060, 'root', 'root', 'main') |
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
120 |
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
121 hostname = socket.gethostname() |
1161 | 122 monitor = PeakMonitor(arg['--source'], METER_RATE) |
887 | 123 for sample in monitor: |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
124 log.debug(' %3d %s', sample, '>' * sample) |
1112
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
125 influx.write_points([{'measurement': 'audioLevel', |
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
126 "tags": dict(stat='max', location=hostname), |
1144
b9981f50b82d
audioInputLevels robustness and cleanup
drewp <drewp@bigasterisk.com>
parents:
1112
diff
changeset
|
127 "fields": {"value": sample / 128}, |
1112
03b4882517dd
audiolevels output to influxdb
drewp <drewp@bigasterisk.com>
parents:
887
diff
changeset
|
128 "time": int(time.time())}], time_precision='s') |
887 | 129 |
130 if __name__ == '__main__': | |
131 main() |