]> git.sesse.net Git - pistorm/blob - a314/files_pi/piaudio.py
Add Meson build files.
[pistorm] / a314 / files_pi / piaudio.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2019 Niklas Ekström
5
6 import fcntl
7 import logging
8 import os
9 import select
10 import socket
11 import struct
12 import sys
13
14 fcntl.F_SETPIPE_SZ = 1031
15
16 logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s')
17 logger = logging.getLogger(__name__)
18 logger.setLevel(logging.INFO)
19
20 MSG_REGISTER_REQ        = 1
21 MSG_REGISTER_RES        = 2
22 MSG_DEREGISTER_REQ      = 3
23 MSG_DEREGISTER_RES      = 4
24 MSG_READ_MEM_REQ        = 5
25 MSG_READ_MEM_RES        = 6
26 MSG_WRITE_MEM_REQ       = 7
27 MSG_WRITE_MEM_RES       = 8
28 MSG_CONNECT             = 9
29 MSG_CONNECT_RESPONSE    = 10
30 MSG_DATA                = 11
31 MSG_EOS                 = 12
32 MSG_RESET               = 13
33
34 def wait_for_msg():
35     header = b''
36     while len(header) < 9:
37         data = drv.recv(9 - len(header))
38         if not data:
39             logger.error('Connection to a314d was closed, terminating.')
40             exit(-1)
41         header += data
42     (plen, stream_id, ptype) = struct.unpack('=IIB', header)
43     payload = b''
44     while len(payload) < plen:
45         data = drv.recv(plen - len(payload))
46         if not data:
47             logger.error('Connection to a314d was closed, terminating.')
48             exit(-1)
49         payload += data
50     return (stream_id, ptype, payload)
51
52 def send_register_req(name):
53     m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name
54     drv.sendall(m)
55
56 def send_read_mem_req(address, length):
57     m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length)
58     drv.sendall(m)
59
60 def read_mem(address, length):
61     send_read_mem_req(address, length)
62     stream_id, ptype, payload = wait_for_msg()
63     if ptype != MSG_READ_MEM_RES:
64         logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype)
65         exit(-1)
66     return payload
67
68 def send_write_mem_req(address, data):
69     m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data
70     drv.sendall(m)
71
72 def write_mem(address, data):
73     send_write_mem_req(address, data)
74     stream_id, ptype, payload = wait_for_msg()
75     if ptype != MSG_WRITE_MEM_RES:
76         logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype)
77         exit(-1)
78
79 def send_connect_response(stream_id, result):
80     m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result)
81     drv.sendall(m)
82
83 def send_data(stream_id, data):
84     m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data
85     drv.sendall(m)
86
87 def send_eos(stream_id):
88     m = struct.pack('=IIB', 0, stream_id, MSG_EOS)
89     drv.sendall(m)
90
91 def send_reset(stream_id):
92     m = struct.pack('=IIB', 0, stream_id, MSG_RESET)
93     drv.sendall(m)
94
95 current_stream_id = None
96 first_msg = True
97 raw_received = b''
98 is_empty = [True, True]
99
100 def process_msg_data(payload):
101     global ptrs, first_msg, raw_received
102
103     if first_msg:
104         ptrs = struct.unpack('>II', payload)
105         logger.debug('Received pointers %s', ptrs)
106         first_msg = False
107         return
108
109     buf_index = payload[0]
110
111     if len(raw_received) < 900*2:
112         if not is_empty[buf_index]:
113             data = b'\x00' * (900*2)
114             send_write_mem_req(ptrs[buf_index], data)
115             is_empty[buf_index] = True
116     else:
117         ldata = raw_received[0:900*2:2]
118         rdata = raw_received[1:900*2:2]
119         data = ldata + rdata
120         raw_received = raw_received[900*2:]
121         send_write_mem_req(ptrs[buf_index], data)
122         is_empty[buf_index] = False
123
124 def process_drv_msg(stream_id, ptype, payload):
125     global current_stream_id, first_msg
126
127     if ptype == MSG_CONNECT:
128         if payload == b'piaudio' and current_stream_id is None:
129             logger.info('Amiga connected')
130             current_stream_id = stream_id
131             first_msg = True
132             send_connect_response(stream_id, 0)
133         else:
134             send_connect_response(stream_id, 3)
135     elif current_stream_id == stream_id:
136         if ptype == MSG_DATA:
137             process_msg_data(payload)
138         elif ptype == MSG_EOS:
139             pass
140         elif ptype == MSG_RESET:
141             current_stream_id = None
142             logger.info('Amiga disconnected')
143
144 done = False
145
146 try:
147     idx = sys.argv.index('-ondemand')
148 except ValueError:
149     idx = -1
150
151 if idx != -1:
152     fd = int(sys.argv[idx + 1])
153     drv = socket.socket(fileno=fd)
154 else:
155     drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
156     drv.connect(('localhost', 7110))
157     drv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
158
159     send_register_req(b'piaudio')
160     _, _, payload = wait_for_msg()
161     if payload[0] != 1:
162         logger.error('Unable to register piaudio with driver, shutting down')
163         drv.close()
164         done = True
165
166 rbuf = b''
167
168 PIPE_NAME = '/tmp/piaudio_pipe'
169
170 if not done:
171     exists = True
172     try:
173         if (os.stat(PIPE_NAME).st_mode & 0o170000) != 0o10000:
174             logger.error('A file that is not a named pipe exists at ' + PIPE_NAME)
175             done = True
176     except:
177         exists = False
178
179 if not done and not exists:
180     try:
181         os.mkfifo(PIPE_NAME)
182     except:
183         logger.error('Unable to create named pipe at ' + PIPE_NAME)
184         done = True
185
186 if not done:
187     try:
188         pipe_fd = os.open(PIPE_NAME, os.O_RDONLY | os.O_NONBLOCK)
189         fcntl.fcntl(pipe_fd, fcntl.F_SETPIPE_SZ, 4096)
190     except:
191         logger.error('Unable to open named pipe at ' + PIPE_NAME)
192         done = True
193
194 if not done:
195     logger.info('piaudio service is running')
196
197 while not done:
198     sel_fds = [drv]
199
200     if idx == -1:
201         sel_fds.append(sys.stdin)
202
203     if len(raw_received) < 900*2:
204         sel_fds.append(pipe_fd)
205
206     rfd, wfd, xfd = select.select(sel_fds, [], [], 5.0)
207
208     for fd in rfd:
209         if fd == sys.stdin:
210             line = sys.stdin.readline()
211             if not line or line.startswith('quit'):
212                 if current_stream_id is not None:
213                     send_reset(current_stream_id)
214                 drv.close()
215                 done = True
216         elif fd == drv:
217             buf = drv.recv(1024)
218             if not buf:
219                 if current_stream_id is not None:
220                     send_reset(current_stream_id)
221                 drv.close()
222                 done = True
223             else:
224                 rbuf += buf
225                 while True:
226                     if len(rbuf) < 9:
227                         break
228
229                     (plen, stream_id, ptype) = struct.unpack('=IIB', rbuf[:9])
230                     if len(rbuf) < 9 + plen:
231                         break
232
233                     rbuf = rbuf[9:]
234                     payload = rbuf[:plen]
235                     rbuf = rbuf[plen:]
236
237                     process_drv_msg(stream_id, ptype, payload)
238         elif fd == pipe_fd:
239             data = os.read(pipe_fd, 900*2)
240
241             if len(data) == 0:
242                 os.close(pipe_fd)
243
244                 l = len(raw_received)
245                 c = l // (900*2)
246                 if c * 900*2 < l:
247                     raw_received += b'\x00' * ((c + 1) * 900*2 - l)
248
249                 pipe_fd = os.open(PIPE_NAME, os.O_RDONLY | os.O_NONBLOCK)
250                 fcntl.fcntl(pipe_fd, fcntl.F_SETPIPE_SZ, 4096)
251             else:
252                 raw_received += data