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()