-
Notifications
You must be signed in to change notification settings - Fork 2
/
iq_af.py
executable file
·178 lines (157 loc) · 6.63 KB
/
iq_af.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/env python
# Program iq_af.py - manage I/Q audio from soundcard using pyaudio
# Copyright (C) 2013-2014 Martin Ewing
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Contact the author by e-mail: [email protected]
#
# Part of the iq.py program.
#
# HISTORY
# 01-04-2014 Initial release (QST article)
# 05-17-2014 timing improvements, esp for Raspberry Pi, etc.
# implement 'skip'
import sys, time, threading
import Queue
import pyaudio as pa
# CALLBACK ROUTINE
# pyaudio callback routine is called when in_data buffer is ready.
# See pyaudio and portaudio documentation for details.
# Callback may not be called at a uniform rate.
# "skip = N" means "discard every (N+1)th buffer" (N > 0) or
# "only use every (-N+1)th buffer" (N < 0)
# i.e. skip=2 -> discard every 3rd buffer;
# skip=-2 -> use every 3rd buffer.
# (skip=1 and skip=-1 have same effect!)
# skip=0 means take all data.
# Global variables (in this module's namespace!)
# globals are required to communicate with callback thread.
led_underrun_ct = 0 # buffer underrun LED
cbcount = 0
MAXQUEUELEN = 32 # Don't use iq-opt for this?
cbqueue = Queue.Queue(MAXQUEUELEN) # will be queue to transmit af data
cbskip_ct = 0
queueLock = threading.Lock() # protect queue accesses
cbfirst = 1 # Skip this many buffers at start
def pa_callback_iqin(in_data, f_c, time_info, status):
global cbcount, cbqueue, cbskip, cbskip_ct
global led_underrun_ct, queueLock, cbfirst
cbcount += 1
if status == pa.paInputOverflow:
led_underrun_ct = 1 # signal LED "underrun" (really, overflow)
# Decide if we should skip this buffer or take it.
# First, are we dropping every Nth buffer?
if cbskip > 0: # Yes, we must check cbskip_ct
if cbskip_ct >= cbskip:
cbskip_ct = 0
return (None, pa.paContinue) # Discard this buffer
else:
cbskip_ct += 1 # OK to process buffer
# Or, are we accepting every Nth buffer?
if cbskip < 0:
if cbskip_ct >= -cbskip:
cbskip_ct = 0 # OK to process buffer
else:
cbskip_ct += 1
return (None, pa.paContinue) # Discard this buffer
# Having decided to take the current buffer, or cbskip==0,
# send it to main thread.
if cbfirst > 0:
cbfirst -= 1
return (None, pa.paContinue) # Toss out first N data
try:
queueLock.acquire()
cbqueue.put_nowait(in_data) # queue should sync with main thread
queueLock.release()
except Queue.Full:
print "ERROR: Internal queue is filled. Reconfigure to use less CPU."
print "\n\n (Ignore remaining errors!)"
#time.sleep(100)
sys.exit()
return (None, pa.paContinue) # Return to pyaudio. All OK.
# END OF CALLBACK ROUTINE
class DataInput(object):
""" Set up audio input with callbacks.
"""
def __init__(self, opt=None):
# Initialize pyaudio (A python mapping of PortAudio)
# Consult pyaudio documentation.
self.audio = pa.PyAudio() # generates lots of warnings.
print
self.Restart(opt)
return
def Restart(self, opt): # Maybe restart after error?
global cbqueue, cbskip
cbskip = opt.skip
print
# set up stereo / 48K IQ input channel. Stream will be started.
if opt.index < 0: # Find pyaudio's idea of default index
defdevinfo = self.audio.get_default_input_device_info()
print "Default device index is %d; id='%s'"% \
(defdevinfo['index'], defdevinfo['name'])
af_using_index = defdevinfo['index']
else:
af_using_index = opt.index # Use user's choice of index
devinfo = self.audio.get_device_info_by_index(af_using_index)
print "Using device index %d; id='%s'" % \
(devinfo['index'], devinfo['name'])
try:
# Verify this is a supported mode.
support = self.audio.is_format_supported(
input_format=pa.paInt16, # 16 bit samples
input_channels=2, # 2 channels
rate=opt.sample_rate, # typ. 48000
input_device=af_using_index)
except ValueError as e:
print "ERROR self.audio.is_format_supported", e
sys.exit()
print "Requested audio mode is supported:", support
self.afiqstream = self.audio.open(
format=pa.paInt16, # 16 bit samples
channels=2, # 2 channels
rate=opt.sample_rate, # typ. 48000
frames_per_buffer= opt.buffers * opt.size,
input_device_index=af_using_index,
input=True, # being used for input only
stream_callback=pa_callback_iqin )
return
def get_queued_data(self):
timeout = 40
while cbqueue.qsize() < 4:
timeout -= 1
if timeout <= 0:
print "timeout waiting for queue to become non-empty!"
sys.exit()
time.sleep(.1)
queueLock.acquire()
data = cbqueue.get(True, 4.) # Why addnl timeout set?
queueLock.release()
return data
def CPU_load(self):
load = self.afiqstream.get_cpu_load()
return load
def isActive(self):
return self.afiqstream.is_active()
def Start(self): # Start pyaudio stream
self.afiqstream.start_stream()
def Stop(self): # Stop pyaudio stream
self.afiqstream.stop_stream()
def CloseStream(self):
self.afiqstream.stop_stream()
self.afiqstream.close()
def Terminate(self): # Stop and release all resources
self.audio.terminate()
if __name__ == '__main__':
print 'debug' # Insert module test code below