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
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
887
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
1 # based on http://freshfoo.com/blog/pulseaudio_monitoring
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
2 from __future__ import division
1161
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
3 import socket, time, logging, os
887
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
4 from Queue import Queue
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
5 from ctypes import POINTER, c_ubyte, c_void_p, c_ulong, cast
1161
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
8
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
9 # From https://github.com/Valodim/python-pulseaudio
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
10 from pulseaudio import lib_pulseaudio as P
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
15 METER_RATE = 1
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
16
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
17 class PeakMonitor(object):
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
18
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
19 def __init__(self, source_name, rate):
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
20 self.source_name = source_name
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
21 self.rate = rate
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
22
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
23 # Wrap callback methods in appropriate ctypefunc instances so
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
24 # that the Pulseaudio C API can call them
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
25 self._context_notify_cb = P.pa_context_notify_cb_t(self.context_notify_cb)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
26 self._source_info_cb = P.pa_source_info_cb_t(self.source_info_cb)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
27 self._stream_read_cb = P.pa_stream_request_cb_t(self.stream_read_cb)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
28
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
29 # stream_read_cb() puts peak samples into this Queue instance
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
30 self._samples = Queue()
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
31
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
32 # Create the mainloop thread and set our context_notify_cb
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
33 # method to be called when there's updates relating to the
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
34 # connection to Pulseaudio
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
35 _mainloop = P.pa_threaded_mainloop_new()
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
36 _mainloop_api = P.pa_threaded_mainloop_get_api(_mainloop)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
37 context = P.pa_context_new(_mainloop_api, 'peak_demo')
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
38 P.pa_context_set_state_callback(context, self._context_notify_cb, None)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
39 P.pa_context_connect(context, None, 0, None)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
40 P.pa_threaded_mainloop_start(_mainloop)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
41
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
42 def __iter__(self):
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
43 while True:
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
44 yield self._samples.get()
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
45
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
46 def context_notify_cb(self, context, _):
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
47 state = P.pa_context_get_state(context)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
48
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
51 # Connected to Pulseaudio. Now request that source_info_cb
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
52 # be called with information about the available sources.
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
53 o = P.pa_context_get_source_info_list(context, self._source_info_cb, None)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
54 P.pa_operation_unref(o)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
55
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
59
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
66
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
67 def source_info_cb(self, context, source_info_p, _, __):
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
68 if not source_info_p:
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
69 return
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
70
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
71 source_info = source_info_p.contents
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
72
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
73 if source_info.name == self.source_name:
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
74 # Found the source we want to monitor for peak levels.
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
79 samplespec = P.pa_sample_spec()
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
80 samplespec.channels = 1
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
81 samplespec.format = P.PA_SAMPLE_U8
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
85 P.pa_stream_set_read_callback(pa_stream,
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
86 self._stream_read_cb,
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
87 source_info.index)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
88 P.pa_stream_connect_record(pa_stream,
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
89 source_info.name,
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
90 None,
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
91 P.PA_STREAM_PEAK_DETECT)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
92
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
93 def stream_read_cb(self, stream, length, index_incr):
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
94 data = c_void_p()
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
95 P.pa_stream_peek(stream, data, c_ulong(length))
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
107 P.pa_stream_drop(stream)
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
108
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
109 def main():
1161
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
110 arg = docopt("""
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
111 Usage: audioInputLevelsPulse.py [-v] --source=<name>
887
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
112
1161
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
113 --source=<name> pulseaudio source name (use `pactl list sources | grep Name`)
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
114 -v Verbose
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
115 """)
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
116
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
117 log.setLevel(logging.DEBUG if arg['-v'] else logging.INFO)
887
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
6139847a72d4 switch to docopt, add -v
drewp <drewp@bigasterisk.com>
parents: 1144
diff changeset
122 monitor = PeakMonitor(arg['--source'], METER_RATE)
887
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
129
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
130 if __name__ == '__main__':
64a2ba088665 audio input levels to graphite
drewp <drewp@bigasterisk.com>
parents:
diff changeset
131 main()