blob: 67c424a7678ec96de922b61e843df4ab02ae8a0b [file] [log] [blame]
Guido van Rossum50692d61991-09-15 21:05:15 +00001# intercom -- use mike and headset to *talk* to a person on another host.
2# For SGI 4D/35 or Indigo running IRIX 4.0.
3# Uses 16 bit sampling at 16000 samples/sec, or 32000 bytes/sec,
4# tranmitted in 32 1000-byte UDP packets. (In each direction!)
5#
6# usage:
7# intercom hostname - start talking to person on other host
8# intercom -r hostname - called remotely to do the setup
9
10import sys, time, posix, gl, fl, FL, al, AL, getopt, rand
11from socket import *
12
13# Hack sys.path so AL can be found
14LIB = '/ufs/guido/lib/python'
15if LIB not in sys.path: sys.path.insert(0, LIB)
16
17# Python binary to be used on remote machine
18PYTHON = '/ufs/guido/bin/sgi/python'
19
20# Directory where the programs live
21AUDIODIR = '/ufs/guido/mm/demo/audio'
22
23# UDP port numbers used (one for each direction!)
24PORT1 = 51042
25PORT2 = PORT1+1
26
27# Figure out the user name
28try:
29 user = posix.environ['LOGNAME']
30except:
31 user = posix.environ['USER']
32
33# Debug flags (Implemented as a list; non-empty means debugging is on)
34debug = []
35
36def main():
37 remote = 0
38 opts, args = getopt.getopt(sys.argv[1:], 'rd')
39 for opt, arg in opts:
40 if opt = '-r': remote = 1
41 elif opt = '-d': debug.append(opt)
42 if len(args) <> 1:
43 msg = 'usage: intercom [-d] [-r] hostname'
44 msg = msg + ' (-r is for internal use only!)\n'
45 sys.stderr.write(msg)
46 sys.exit(2)
47 if remote:
48 server(args[0])
49 else:
50 client(args[0])
51
52def client(hostname):
53 print 'client starting'
54 cmd = 'rsh ' + hostname + ' "cd ' + AUDIODIR
55 cmd = cmd + '; DISPLAY=:0; export DISPLAY'
56 cmd = cmd + '; exec ' + PYTHON + ' intercom.py -r '
57 for flag in debug: cmd = cmd + flag + ' '
58 cmd = cmd + gethostname()
59 cmd = cmd + '"'
60 pipe = posix.popen(cmd, 'r')
61 ack = 0
62 nak = 0
63 while 1:
64 line = pipe.readline()
65 if not line: break
66 sys.stdout.write('remote: ' + line)
67 if line = 'NAK\n':
68 nak = 1
69 break
70 elif line = 'ACK\n':
71 ack = 1
72 break
73 if nak:
74 print 'Remote user doesn\'t want to talk to you.'
75 return
76 if not ack:
77 print 'No acknowledgement (remote side crashed?).'
78 return
79 #
80 print 'Ready...'
81 #
82 s = socket(AF_INET, SOCK_DGRAM)
83 s.bind('', PORT2)
84 #
85 otheraddr = gethostbyname(hostname), PORT1
86 try:
87 ioloop(s, otheraddr)
88 except KeyboardInterrupt:
89 log('client got intr')
90 except error:
91 log('client got error')
92 finally:
93 s.sendto('', otheraddr)
94 log('client finished sending empty packet to server')
95 #
96 log('client exit')
97 print 'Done.'
98
99def server(hostname):
100 print 'server starting'
101 sys.stdout.flush()
102 #
103 if not remotedialog():
104 print 'NAK'
105 return
106 #
107 print 'ACK'
108 #
109 s = socket(AF_INET, SOCK_DGRAM)
110 s.bind('', PORT1)
111 #
112 # Close std{in,out,err} so rsh will exit; reopen them as dummies
113 #
114 sys.stdin.close()
115 sys.stdin = open('/dev/null', 'r')
116 sys.stdout.close()
117 sys.stdout = open('/dev/null', 'w')
118 sys.stderr.close()
119 if debug:
120 sys.stderr = open('/tmp/intercom.err', 'a')
121 else:
122 sys.stderr = open('/dev/null', 'w')
123 #
124 ioloop(s, (gethostbyname(hostname), PORT2))
125 log('server exit')
126 sys.exit(0)
127
128def remotedialog():
129 gl.foreground()
130 gl.ringbell()
131 m1 = user + ' wants to talk to you over the audio channel.'
132 m2 = 'If it\'s OK, put on your headset and click Yes.'
133 m3 = 'If you\'re too busy, click No.'
134 return fl.show_question(m1, m2, m3)
135
136def ioloop(s, otheraddr):
137 #
138 dev = AL.DEFAULT_DEVICE
139 params = al.queryparams(dev)
140 al.getparams(dev, params)
141 time.sleep(1)
142 saveparams = params[:]
143 for i in range(0, len(params), 2):
144 if params[i] in (AL.INPUT_RATE, AL.OUTPUT_RATE):
145 params[i+1] = AL.RATE_16000
146 elif params[i] = AL.INPUT_SOURCE:
147 params[i+1] = AL.INPUT_MIC
148 try:
149 al.setparams(dev, params)
150 ioloop1(s, otheraddr)
151 finally:
152 al.setparams(dev, saveparams)
153
154def ioloop1(s, otheraddr):
155 #
156 # Watch out! data is in bytes, but the port counts in samples,
157 # which are two bytes each (for 16-bit samples).
158 # Luckily, we use mono, else it would be worse (2 samples/frame...)
159 #
160 SAMPSPERBUF = 500
161 BYTESPERSAMP = 2 # AL.SAMPLE_16
162 BUFSIZE = BYTESPERSAMP*SAMPSPERBUF
163 QSIZE = 4*SAMPSPERBUF
164 #
165 config = al.newconfig()
166 config.setqueuesize(QSIZE)
167 config.setwidth(AL.SAMPLE_16)
168 config.setchannels(AL.MONO)
169 #
170 pid = posix.fork()
171 if pid:
172 # Parent -- speaker/headphones handler
173 log('parent started')
174 spkr = al.openport('spkr', 'w', config)
175 while 1:
176 data = s.recv(BUFSIZE)
177 if len(data) = 0:
178 # EOF packet
179 log('parent got empty packet; killing child')
180 posix.kill(pid, 15)
181 return
182 # Discard whole packet if we are too much behind
183 if spkr.getfillable() > len(data) / BYTESPERSAMP:
184 if len(debug) >= 2:
185 log('parent Q full; dropping packet')
186 spkr.writesamps(data)
187 else:
188 # Child -- microphone handler
189 log('child started')
190 try:
191 mike = al.openport('mike', 'r', config)
192 # Sleep a while to let the other side get started
193 time.sleep(1)
194 # Drain the queue before starting to read
195 data = mike.readsamps(mike.getfilled())
196 # Loop, sending packets from the mike to the net
197 while 1:
198 data = mike.readsamps(SAMPSPERBUF)
199 s.sendto(data, otheraddr)
200 except KeyboardInterrupt:
201 log('child got interrupt; exiting')
202 posix._exit(0)
203 except error:
204 log('child got error; exiting')
205 posix._exit(1)
206 finally:
207 log('child got unexpected error; leaving w/ traceback')
208
209def log(msg):
210 if not debug: return
211 if type(msg) <> type(''):
212 msg = `msg`
213
214 f = open('/tmp/intercom.log', 'a')
215 f.write(`sys.argv` + ' ' + `posix.getpid()` + ': ' + msg + '\n')
216 f.close()
217
218main()