c@125
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@125
|
2 /*
|
c@125
|
3 Piper C++
|
c@125
|
4
|
c@125
|
5 An API for audio analysis and feature extraction plugins.
|
c@125
|
6
|
c@125
|
7 Centre for Digital Music, Queen Mary, University of London.
|
c@125
|
8 Copyright 2006-2016 Chris Cannam and QMUL.
|
c@125
|
9
|
c@125
|
10 Permission is hereby granted, free of charge, to any person
|
c@125
|
11 obtaining a copy of this software and associated documentation
|
c@125
|
12 files (the "Software"), to deal in the Software without
|
c@125
|
13 restriction, including without limitation the rights to use, copy,
|
c@125
|
14 modify, merge, publish, distribute, sublicense, and/or sell copies
|
c@125
|
15 of the Software, and to permit persons to whom the Software is
|
c@125
|
16 furnished to do so, subject to the following conditions:
|
c@125
|
17
|
c@125
|
18 The above copyright notice and this permission notice shall be
|
c@125
|
19 included in all copies or substantial portions of the Software.
|
c@125
|
20
|
c@125
|
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
c@125
|
22 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
c@125
|
23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
c@125
|
24 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
c@125
|
25 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
c@125
|
26 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
c@125
|
27 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
c@125
|
28
|
c@125
|
29 Except as contained in this notice, the names of the Centre for
|
c@125
|
30 Digital Music; Queen Mary, University of London; and Chris Cannam
|
c@125
|
31 shall not be used in advertising or otherwise to promote the sale,
|
c@125
|
32 use or other dealings in this Software without prior written
|
c@125
|
33 authorization.
|
c@125
|
34 */
|
c@125
|
35
|
c@125
|
36 #include "vamp-json/VampJson.h"
|
c@125
|
37 #include "vamp-capnp/VampnProto.h"
|
c@125
|
38 #include "vamp-support/RequestOrResponse.h"
|
c@125
|
39 #include "vamp-support/CountingPluginHandleMapper.h"
|
c@125
|
40 #include "vamp-support/LoaderRequests.h"
|
cannam@222
|
41 #include "vamp-support/RdfTypes.h"
|
c@125
|
42
|
c@125
|
43 #include <iostream>
|
c@125
|
44 #include <sstream>
|
c@125
|
45 #include <stdexcept>
|
c@125
|
46
|
c@125
|
47 #include <capnp/serialize.h>
|
c@125
|
48
|
c@125
|
49 #include <map>
|
c@125
|
50 #include <set>
|
c@125
|
51
|
c@125
|
52 // pid for logging
|
c@125
|
53 #ifdef _WIN32
|
c@125
|
54 #include <process.h>
|
c@125
|
55 static int pid = _getpid();
|
c@125
|
56 #else
|
c@125
|
57 #include <unistd.h>
|
c@125
|
58 static int pid = getpid();
|
c@125
|
59 #endif
|
c@125
|
60
|
c@138
|
61 // for _setmode stuff and _dup
|
c@125
|
62 #ifdef _WIN32
|
c@125
|
63 #include <io.h>
|
c@125
|
64 #include <fcntl.h>
|
c@125
|
65 #endif
|
c@125
|
66
|
c@138
|
67 // for dup, open etc
|
c@138
|
68 #ifndef _WIN32
|
c@138
|
69 #include <fcntl.h>
|
c@138
|
70 #include <unistd.h>
|
c@138
|
71 #endif
|
c@138
|
72
|
c@125
|
73 using namespace std;
|
c@125
|
74 using namespace json11;
|
c@125
|
75 using namespace piper_vamp;
|
c@125
|
76 using namespace Vamp;
|
c@125
|
77
|
c@125
|
78 static string myname = "piper-vamp-simple-server";
|
c@125
|
79
|
c@125
|
80 static void version()
|
c@125
|
81 {
|
c@125
|
82 cout << "1.0" << endl;
|
c@125
|
83 exit(0);
|
c@125
|
84 }
|
c@125
|
85
|
c@125
|
86 static void usage(bool successful = false)
|
c@125
|
87 {
|
c@125
|
88 cerr << "\n" << myname <<
|
c@125
|
89 ": Load & run Vamp plugins in response to Piper messages\n\n"
|
c@125
|
90 " Usage: " << myname << " [-d] <format>\n"
|
c@125
|
91 " " << myname << " -v\n"
|
c@125
|
92 " " << myname << " -h\n\n"
|
c@125
|
93 " where\n"
|
c@125
|
94 " <format>: the format to read and write messages in (\"json\" or \"capnp\")\n"
|
c@125
|
95 " -d: also print debug information to stderr\n"
|
c@125
|
96 " -v: print version number to stdout and exit\n"
|
c@125
|
97 " -h: print this text to stderr and exit\n\n"
|
c@125
|
98 "Expects Piper request messages in either Cap'n Proto or JSON format on stdin,\n"
|
c@125
|
99 "and writes response messages in the same format to stdout.\n\n"
|
c@125
|
100 "This server is intended for simple process separation. It's only suitable for\n"
|
c@125
|
101 "use with a single trusted client per server invocation.\n\n"
|
c@125
|
102 "The two formats behave differently in case of parser errors. JSON messages are\n"
|
c@125
|
103 "expected one per input line; because the JSON support is really intended for\n"
|
c@125
|
104 "interactive troubleshooting, any unparseable message is reported and discarded\n"
|
c@125
|
105 "and the server waits for another message. In contrast, because of the assumption\n"
|
c@125
|
106 "that the client is trusted and coupled to the server instance, a mangled\n"
|
c@125
|
107 "Cap'n Proto message causes the server to exit.\n\n";
|
c@125
|
108 if (successful) exit(0);
|
c@125
|
109 else exit(2);
|
c@125
|
110 }
|
c@125
|
111
|
c@125
|
112 static CountingPluginHandleMapper mapper;
|
c@125
|
113
|
c@138
|
114 // We write our output to stdout, but want to ensure that the plugin
|
c@138
|
115 // doesn't write anything itself. To do this we open a null file
|
c@138
|
116 // descriptor and dup2() it into place of stdout in the gaps between
|
c@138
|
117 // our own output activity.
|
c@138
|
118
|
c@138
|
119 static int normalFd = -1;
|
c@138
|
120 static int suspendedFd = -1;
|
c@138
|
121
|
c@138
|
122 static void initFds(bool binary)
|
c@138
|
123 {
|
c@138
|
124 #ifdef _WIN32
|
c@138
|
125 if (binary) {
|
c@138
|
126 int result = _setmode(0, _O_BINARY);
|
c@138
|
127 if (result == -1) {
|
c@138
|
128 throw runtime_error("Failed to set binary mode on stdin");
|
c@138
|
129 }
|
c@138
|
130 result = _setmode(1, _O_BINARY);
|
c@138
|
131 if (result == -1) {
|
c@138
|
132 throw runtime_error("Failed to set binary mode on stdout");
|
c@138
|
133 }
|
c@138
|
134 }
|
c@138
|
135 normalFd = _dup(1);
|
c@138
|
136 suspendedFd = _open("NUL", _O_WRONLY);
|
c@138
|
137 #else
|
c@141
|
138 (void)binary;
|
c@138
|
139 normalFd = dup(1);
|
c@138
|
140 suspendedFd = open("/dev/null", O_WRONLY);
|
c@138
|
141 #endif
|
c@138
|
142
|
c@138
|
143 if (normalFd < 0 || suspendedFd < 0) {
|
c@138
|
144 throw runtime_error("Failed to initialise fds for stdio suspend/resume");
|
c@138
|
145 }
|
c@138
|
146 }
|
c@138
|
147
|
c@138
|
148 static void suspendOutput()
|
c@138
|
149 {
|
c@138
|
150 #ifdef _WIN32
|
c@138
|
151 _dup2(suspendedFd, 1);
|
c@138
|
152 #else
|
c@138
|
153 dup2(suspendedFd, 1);
|
c@138
|
154 #endif
|
c@138
|
155 }
|
c@138
|
156
|
c@138
|
157 static void resumeOutput()
|
c@138
|
158 {
|
c@138
|
159 #ifdef _WIN32
|
c@138
|
160 _dup2(normalFd, 1);
|
c@138
|
161 #else
|
c@138
|
162 dup2(normalFd, 1);
|
c@138
|
163 #endif
|
c@138
|
164 }
|
c@138
|
165
|
c@125
|
166 static RequestOrResponse::RpcId
|
c@125
|
167 readId(const piper::RpcRequest::Reader &r)
|
c@125
|
168 {
|
c@125
|
169 int number;
|
c@125
|
170 string tag;
|
c@125
|
171 switch (r.getId().which()) {
|
c@125
|
172 case piper::RpcRequest::Id::Which::NUMBER:
|
c@125
|
173 number = r.getId().getNumber();
|
c@125
|
174 return { RequestOrResponse::RpcId::Number, number, "" };
|
c@125
|
175 case piper::RpcRequest::Id::Which::TAG:
|
c@125
|
176 tag = r.getId().getTag();
|
c@125
|
177 return { RequestOrResponse::RpcId::Tag, 0, tag };
|
c@125
|
178 case piper::RpcRequest::Id::Which::NONE:
|
c@125
|
179 return { RequestOrResponse::RpcId::Absent, 0, "" };
|
c@125
|
180 }
|
cannam@154
|
181 return { RequestOrResponse::RpcId::Absent, 0, "" };
|
c@125
|
182 }
|
c@125
|
183
|
c@125
|
184 static void
|
c@125
|
185 buildId(piper::RpcResponse::Builder &b, const RequestOrResponse::RpcId &id)
|
c@125
|
186 {
|
c@125
|
187 switch (id.type) {
|
c@125
|
188 case RequestOrResponse::RpcId::Number:
|
c@125
|
189 b.getId().setNumber(id.number);
|
c@125
|
190 break;
|
c@125
|
191 case RequestOrResponse::RpcId::Tag:
|
c@125
|
192 b.getId().setTag(id.tag);
|
c@125
|
193 break;
|
c@125
|
194 case RequestOrResponse::RpcId::Absent:
|
c@125
|
195 b.getId().setNone();
|
c@125
|
196 break;
|
c@125
|
197 }
|
c@125
|
198 }
|
c@125
|
199
|
c@125
|
200 static RequestOrResponse::RpcId
|
c@125
|
201 readJsonId(const Json &j)
|
c@125
|
202 {
|
c@125
|
203 RequestOrResponse::RpcId id;
|
c@125
|
204
|
c@125
|
205 if (j["id"].is_number()) {
|
c@125
|
206 id.type = RequestOrResponse::RpcId::Number;
|
c@140
|
207 id.number = int(round(j["id"].number_value()));
|
c@125
|
208 } else if (j["id"].is_string()) {
|
c@125
|
209 id.type = RequestOrResponse::RpcId::Tag;
|
c@125
|
210 id.tag = j["id"].string_value();
|
c@125
|
211 } else {
|
c@125
|
212 id.type = RequestOrResponse::RpcId::Absent;
|
c@125
|
213 }
|
c@125
|
214
|
c@125
|
215 return id;
|
c@125
|
216 }
|
c@125
|
217
|
c@125
|
218 static Json
|
c@125
|
219 writeJsonId(const RequestOrResponse::RpcId &id)
|
c@125
|
220 {
|
c@125
|
221 if (id.type == RequestOrResponse::RpcId::Number) {
|
c@125
|
222 return id.number;
|
c@125
|
223 } else if (id.type == RequestOrResponse::RpcId::Tag) {
|
c@125
|
224 return id.tag;
|
c@125
|
225 } else {
|
c@125
|
226 return Json();
|
c@125
|
227 }
|
c@125
|
228 }
|
c@125
|
229
|
c@125
|
230 static Json
|
c@125
|
231 convertRequestJson(string input, string &err)
|
c@125
|
232 {
|
c@125
|
233 Json j = Json::parse(input, err);
|
c@125
|
234 if (err != "") {
|
c@125
|
235 err = "invalid json: " + err;
|
c@125
|
236 return {};
|
c@125
|
237 }
|
c@125
|
238 if (!j.is_object()) {
|
c@125
|
239 err = "object expected at top level";
|
c@125
|
240 } else if (!j["method"].is_string()) {
|
c@125
|
241 err = "string expected for method field";
|
c@125
|
242 } else if (!j["params"].is_null() && !j["params"].is_object()) {
|
c@125
|
243 err = "object expected for params field";
|
c@125
|
244 }
|
c@125
|
245 return j;
|
c@125
|
246 }
|
c@125
|
247
|
c@125
|
248 RequestOrResponse
|
cannam@158
|
249 readRequestJson(string &err, bool &eof)
|
c@125
|
250 {
|
c@125
|
251 RequestOrResponse rr;
|
c@125
|
252 rr.direction = RequestOrResponse::Request;
|
c@125
|
253
|
c@125
|
254 string input;
|
c@125
|
255 if (!getline(cin, input)) {
|
c@125
|
256 // the EOF case, not actually an error
|
cannam@158
|
257 eof = true;
|
c@125
|
258 return rr;
|
c@125
|
259 }
|
c@125
|
260
|
c@125
|
261 Json j = convertRequestJson(input, err);
|
c@125
|
262 if (err != "") return {};
|
c@125
|
263
|
c@125
|
264 rr.type = VampJson::getRequestResponseType(j, err);
|
c@125
|
265 if (err != "") return {};
|
c@125
|
266
|
c@125
|
267 rr.id = readJsonId(j);
|
c@125
|
268
|
c@125
|
269 VampJson::BufferSerialisation serialisation =
|
c@125
|
270 VampJson::BufferSerialisation::Array;
|
c@125
|
271
|
c@125
|
272 switch (rr.type) {
|
c@125
|
273
|
c@125
|
274 case RRType::List:
|
c@130
|
275 rr.listRequest = VampJson::toRpcRequest_List(j, err);
|
c@125
|
276 break;
|
c@125
|
277 case RRType::Load:
|
c@125
|
278 rr.loadRequest = VampJson::toRpcRequest_Load(j, err);
|
c@125
|
279 break;
|
c@125
|
280 case RRType::Configure:
|
c@125
|
281 rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err);
|
c@125
|
282 break;
|
c@125
|
283 case RRType::Process:
|
c@125
|
284 rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err);
|
c@125
|
285 break;
|
c@125
|
286 case RRType::Finish:
|
c@125
|
287 rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err);
|
c@125
|
288 break;
|
c@125
|
289 case RRType::NotValid:
|
c@125
|
290 break;
|
c@125
|
291 }
|
c@125
|
292
|
c@125
|
293 return rr;
|
c@125
|
294 }
|
c@125
|
295
|
c@125
|
296 void
|
c@125
|
297 writeResponseJson(RequestOrResponse &rr, bool useBase64)
|
c@125
|
298 {
|
c@125
|
299 Json j;
|
c@125
|
300
|
c@125
|
301 VampJson::BufferSerialisation serialisation =
|
c@125
|
302 (useBase64 ?
|
c@125
|
303 VampJson::BufferSerialisation::Base64 :
|
c@125
|
304 VampJson::BufferSerialisation::Array);
|
c@125
|
305
|
c@125
|
306 Json id = writeJsonId(rr.id);
|
c@125
|
307
|
c@125
|
308 if (!rr.success) {
|
c@125
|
309
|
c@125
|
310 j = VampJson::fromError(rr.errorText, rr.type, id);
|
c@125
|
311
|
c@125
|
312 } else {
|
c@125
|
313
|
c@125
|
314 switch (rr.type) {
|
c@125
|
315
|
c@125
|
316 case RRType::List:
|
c@125
|
317 j = VampJson::fromRpcResponse_List(rr.listResponse, id);
|
c@125
|
318 break;
|
c@125
|
319 case RRType::Load:
|
c@125
|
320 j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id);
|
c@125
|
321 break;
|
c@125
|
322 case RRType::Configure:
|
c@125
|
323 j = VampJson::fromRpcResponse_Configure(rr.configurationResponse,
|
c@125
|
324 mapper, id);
|
c@125
|
325 break;
|
c@125
|
326 case RRType::Process:
|
c@125
|
327 j = VampJson::fromRpcResponse_Process
|
c@125
|
328 (rr.processResponse, mapper, serialisation, id);
|
c@125
|
329 break;
|
c@125
|
330 case RRType::Finish:
|
c@125
|
331 j = VampJson::fromRpcResponse_Finish
|
c@125
|
332 (rr.finishResponse, mapper, serialisation, id);
|
c@125
|
333 break;
|
c@125
|
334 case RRType::NotValid:
|
c@125
|
335 break;
|
c@125
|
336 }
|
c@125
|
337 }
|
c@138
|
338
|
c@125
|
339 cout << j.dump() << endl;
|
c@125
|
340 }
|
c@125
|
341
|
c@125
|
342 void
|
cannam@158
|
343 writeExceptionJson(const exception &e, RRType type, RequestOrResponse::RpcId id)
|
c@125
|
344 {
|
cannam@158
|
345 Json jid = writeJsonId(id);
|
cannam@158
|
346 Json j = VampJson::fromError(e.what(), type, jid);
|
c@125
|
347 cout << j.dump() << endl;
|
c@125
|
348 }
|
c@125
|
349
|
c@125
|
350 RequestOrResponse
|
cannam@158
|
351 readRequestCapnp(bool &eof)
|
c@125
|
352 {
|
c@125
|
353 RequestOrResponse rr;
|
c@125
|
354 rr.direction = RequestOrResponse::Request;
|
c@125
|
355
|
c@125
|
356 static kj::FdInputStream stream(0); // stdin
|
c@125
|
357 static kj::BufferedInputStreamWrapper buffered(stream);
|
c@125
|
358
|
c@125
|
359 if (buffered.tryGetReadBuffer() == nullptr) {
|
cannam@158
|
360 eof = true;
|
c@125
|
361 return rr;
|
c@125
|
362 }
|
c@125
|
363
|
c@125
|
364 capnp::InputStreamMessageReader message(buffered);
|
c@125
|
365 piper::RpcRequest::Reader reader = message.getRoot<piper::RpcRequest>();
|
c@125
|
366
|
c@125
|
367 rr.type = VampnProto::getRequestResponseType(reader);
|
c@125
|
368 rr.id = readId(reader);
|
c@125
|
369
|
c@125
|
370 switch (rr.type) {
|
c@125
|
371
|
c@125
|
372 case RRType::List:
|
c@127
|
373 VampnProto::readRpcRequest_List(rr.listRequest, reader);
|
c@125
|
374 break;
|
c@125
|
375 case RRType::Load:
|
c@125
|
376 VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
|
c@125
|
377 break;
|
c@125
|
378 case RRType::Configure:
|
c@125
|
379 VampnProto::readRpcRequest_Configure(rr.configurationRequest,
|
c@125
|
380 reader, mapper);
|
c@125
|
381 break;
|
c@125
|
382 case RRType::Process:
|
c@125
|
383 VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
|
c@125
|
384 break;
|
c@125
|
385 case RRType::Finish:
|
c@125
|
386 VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
|
c@125
|
387 break;
|
c@125
|
388 case RRType::NotValid:
|
c@125
|
389 break;
|
c@125
|
390 }
|
c@125
|
391
|
c@125
|
392 return rr;
|
c@125
|
393 }
|
c@125
|
394
|
c@125
|
395 void
|
c@125
|
396 writeResponseCapnp(RequestOrResponse &rr)
|
c@125
|
397 {
|
c@125
|
398 capnp::MallocMessageBuilder message;
|
c@125
|
399 piper::RpcResponse::Builder builder = message.initRoot<piper::RpcResponse>();
|
c@125
|
400
|
c@125
|
401 buildId(builder, rr.id);
|
c@125
|
402
|
c@125
|
403 if (!rr.success) {
|
c@125
|
404
|
c@125
|
405 VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
|
c@125
|
406
|
c@125
|
407 } else {
|
c@125
|
408
|
c@125
|
409 switch (rr.type) {
|
c@125
|
410
|
c@125
|
411 case RRType::List:
|
c@125
|
412 VampnProto::buildRpcResponse_List(builder, rr.listResponse);
|
c@125
|
413 break;
|
c@125
|
414 case RRType::Load:
|
c@125
|
415 VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
|
c@125
|
416 break;
|
c@125
|
417 case RRType::Configure:
|
c@125
|
418 VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
|
c@125
|
419 break;
|
c@125
|
420 case RRType::Process:
|
c@125
|
421 VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
|
c@125
|
422 break;
|
c@125
|
423 case RRType::Finish:
|
c@125
|
424 VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
|
c@125
|
425 break;
|
c@125
|
426 case RRType::NotValid:
|
c@125
|
427 break;
|
c@125
|
428 }
|
c@125
|
429 }
|
c@125
|
430
|
c@125
|
431 writeMessageToFd(1, message);
|
c@125
|
432 }
|
c@125
|
433
|
c@125
|
434 void
|
cannam@158
|
435 writeExceptionCapnp(const exception &e, RRType type, RequestOrResponse::RpcId id)
|
c@125
|
436 {
|
c@125
|
437 capnp::MallocMessageBuilder message;
|
c@125
|
438 piper::RpcResponse::Builder builder = message.initRoot<piper::RpcResponse>();
|
cannam@158
|
439
|
cannam@158
|
440 buildId(builder, id);
|
c@125
|
441 VampnProto::buildRpcResponse_Exception(builder, e, type);
|
c@125
|
442
|
c@125
|
443 writeMessageToFd(1, message);
|
c@125
|
444 }
|
c@125
|
445
|
c@125
|
446 RequestOrResponse
|
c@125
|
447 handleRequest(const RequestOrResponse &request, bool debug)
|
c@125
|
448 {
|
c@125
|
449 RequestOrResponse response;
|
c@125
|
450 response.direction = RequestOrResponse::Response;
|
c@125
|
451 response.type = request.type;
|
c@125
|
452
|
c@125
|
453 switch (request.type) {
|
c@125
|
454
|
c@125
|
455 case RRType::List:
|
c@127
|
456 response.listResponse =
|
c@127
|
457 LoaderRequests().listPluginData(request.listRequest);
|
c@125
|
458 response.success = true;
|
c@125
|
459 break;
|
c@125
|
460
|
c@125
|
461 case RRType::Load:
|
c@127
|
462 response.loadResponse =
|
c@127
|
463 LoaderRequests().loadPlugin(request.loadRequest);
|
c@125
|
464 if (response.loadResponse.plugin != nullptr) {
|
c@125
|
465 mapper.addPlugin(response.loadResponse.plugin);
|
c@125
|
466 if (debug) {
|
c@127
|
467 cerr << "piper-vamp-server " << pid
|
c@127
|
468 << ": loaded plugin, handle = "
|
c@127
|
469 << mapper.pluginToHandle(response.loadResponse.plugin)
|
c@127
|
470 << endl;
|
c@125
|
471 }
|
c@125
|
472 response.success = true;
|
c@125
|
473 }
|
c@125
|
474 break;
|
c@125
|
475
|
c@125
|
476 case RRType::Configure:
|
c@125
|
477 {
|
c@125
|
478 auto &creq = request.configurationRequest;
|
cannam@158
|
479 if (!creq.plugin) {
|
cannam@158
|
480 throw runtime_error("unknown plugin handle supplied to configure");
|
cannam@158
|
481 }
|
cannam@158
|
482
|
c@125
|
483 auto h = mapper.pluginToHandle(creq.plugin);
|
c@125
|
484 if (mapper.isConfigured(h)) {
|
c@125
|
485 throw runtime_error("plugin has already been configured");
|
c@125
|
486 }
|
c@125
|
487
|
cannam@185
|
488 if (creq.configuration.framing.stepSize == 0 ||
|
cannam@185
|
489 creq.configuration.framing.blockSize == 0) {
|
cannam@185
|
490 throw runtime_error("step and block size must be non-zero");
|
cannam@185
|
491 }
|
cannam@185
|
492
|
c@125
|
493 response.configurationResponse = LoaderRequests().configurePlugin(creq);
|
c@125
|
494
|
c@125
|
495 if (!response.configurationResponse.outputs.empty()) {
|
c@125
|
496 mapper.markConfigured
|
cannam@185
|
497 (h,
|
cannam@185
|
498 creq.configuration.channelCount,
|
cannam@186
|
499 response.configurationResponse.framing.blockSize);
|
c@125
|
500 response.success = true;
|
c@125
|
501 }
|
c@125
|
502 break;
|
c@125
|
503 }
|
c@125
|
504
|
c@125
|
505 case RRType::Process:
|
c@125
|
506 {
|
c@125
|
507 auto &preq = request.processRequest;
|
cannam@158
|
508 if (!preq.plugin) {
|
cannam@158
|
509 throw runtime_error("unknown plugin handle supplied to process");
|
cannam@158
|
510 }
|
cannam@158
|
511
|
c@125
|
512 auto h = mapper.pluginToHandle(preq.plugin);
|
c@125
|
513 if (!mapper.isConfigured(h)) {
|
c@125
|
514 throw runtime_error("plugin has not been configured");
|
c@125
|
515 }
|
c@125
|
516
|
c@125
|
517 int channels = int(preq.inputBuffers.size());
|
c@125
|
518 if (channels != mapper.getChannelCount(h)) {
|
c@125
|
519 throw runtime_error("wrong number of channels supplied to process");
|
c@125
|
520 }
|
c@125
|
521
|
c@125
|
522 const float **fbuffers = new const float *[channels];
|
c@125
|
523 for (int i = 0; i < channels; ++i) {
|
c@125
|
524 if (int(preq.inputBuffers[i].size()) != mapper.getBlockSize(h)) {
|
cannam@186
|
525 ostringstream os;
|
cannam@186
|
526 os << "wrong block size supplied to process ("
|
cannam@186
|
527 << preq.inputBuffers[i].size()
|
cannam@186
|
528 << ", expecting " << mapper.getBlockSize(h) << ")" << ends;
|
c@125
|
529 delete[] fbuffers;
|
cannam@186
|
530 throw runtime_error(os.str());
|
c@125
|
531 }
|
c@125
|
532 fbuffers[i] = preq.inputBuffers[i].data();
|
c@125
|
533 }
|
c@125
|
534
|
c@125
|
535 response.processResponse.plugin = preq.plugin;
|
c@125
|
536 response.processResponse.features =
|
c@125
|
537 preq.plugin->process(fbuffers, preq.timestamp);
|
c@125
|
538 response.success = true;
|
c@125
|
539
|
c@125
|
540 delete[] fbuffers;
|
c@125
|
541 break;
|
c@125
|
542 }
|
c@125
|
543
|
c@125
|
544 case RRType::Finish:
|
c@125
|
545 {
|
c@125
|
546 auto &freq = request.finishRequest;
|
cannam@158
|
547 if (!freq.plugin) {
|
cannam@158
|
548 throw runtime_error("unknown plugin handle supplied to finish");
|
cannam@158
|
549 }
|
cannam@158
|
550
|
c@125
|
551 response.finishResponse.plugin = freq.plugin;
|
c@125
|
552
|
c@125
|
553 auto h = mapper.pluginToHandle(freq.plugin);
|
c@125
|
554 // Finish can be called (to unload the plugin) even if the
|
c@125
|
555 // plugin has never been configured or used. But we want to
|
c@125
|
556 // make sure we call getRemainingFeatures only if we have
|
c@125
|
557 // actually configured the plugin.
|
c@125
|
558 if (mapper.isConfigured(h)) {
|
c@125
|
559 response.finishResponse.features = freq.plugin->getRemainingFeatures();
|
c@125
|
560 }
|
c@125
|
561
|
c@125
|
562 // We do not delete the plugin here -- we need it in the
|
c@125
|
563 // mapper when converting the features. It gets deleted in the
|
c@125
|
564 // calling function.
|
c@125
|
565 response.success = true;
|
c@125
|
566 break;
|
c@125
|
567 }
|
c@125
|
568
|
c@125
|
569 case RRType::NotValid:
|
c@125
|
570 break;
|
c@125
|
571 }
|
c@125
|
572
|
c@125
|
573 return response;
|
c@125
|
574 }
|
c@125
|
575
|
c@125
|
576 RequestOrResponse
|
cannam@158
|
577 readRequest(string format, bool &eof)
|
c@125
|
578 {
|
c@125
|
579 if (format == "capnp") {
|
cannam@158
|
580 return readRequestCapnp(eof);
|
c@125
|
581 } else if (format == "json") {
|
c@125
|
582 string err;
|
cannam@158
|
583 auto result = readRequestJson(err, eof);
|
c@125
|
584 if (err != "") throw runtime_error(err);
|
c@125
|
585 else return result;
|
c@125
|
586 } else {
|
c@125
|
587 throw runtime_error("unknown input format \"" + format + "\"");
|
c@125
|
588 }
|
c@125
|
589 }
|
c@125
|
590
|
c@125
|
591 void
|
c@125
|
592 writeResponse(string format, RequestOrResponse &rr)
|
c@125
|
593 {
|
c@138
|
594 resumeOutput();
|
c@125
|
595 if (format == "capnp") {
|
c@125
|
596 writeResponseCapnp(rr);
|
c@125
|
597 } else if (format == "json") {
|
c@125
|
598 writeResponseJson(rr, false);
|
c@125
|
599 } else {
|
c@125
|
600 throw runtime_error("unknown output format \"" + format + "\"");
|
c@125
|
601 }
|
c@138
|
602 suspendOutput();
|
c@125
|
603 }
|
c@125
|
604
|
c@125
|
605 void
|
cannam@158
|
606 writeException(string format, const exception &e, RRType type, RequestOrResponse::RpcId id)
|
c@125
|
607 {
|
c@138
|
608 resumeOutput();
|
c@125
|
609 if (format == "capnp") {
|
cannam@158
|
610 writeExceptionCapnp(e, type, id);
|
c@125
|
611 } else if (format == "json") {
|
cannam@158
|
612 writeExceptionJson(e, type, id);
|
c@125
|
613 } else {
|
c@125
|
614 throw runtime_error("unknown output format \"" + format + "\"");
|
c@125
|
615 }
|
c@138
|
616 suspendOutput();
|
c@125
|
617 }
|
c@125
|
618
|
c@125
|
619 int main(int argc, char **argv)
|
c@125
|
620 {
|
c@125
|
621 if (argc != 2 && argc != 3) {
|
c@125
|
622 usage();
|
c@125
|
623 }
|
c@125
|
624
|
c@125
|
625 bool debug = false;
|
c@125
|
626
|
c@125
|
627 string arg = argv[1];
|
c@125
|
628 if (arg == "-h") {
|
c@125
|
629 if (argc == 2) {
|
c@125
|
630 usage(true);
|
c@125
|
631 } else {
|
c@125
|
632 usage();
|
c@125
|
633 }
|
c@125
|
634 } else if (arg == "-v") {
|
c@125
|
635 if (argc == 2) {
|
c@125
|
636 version();
|
c@125
|
637 } else {
|
c@125
|
638 usage();
|
c@125
|
639 }
|
c@125
|
640 } else if (arg == "-d") {
|
c@125
|
641 if (argc == 2) {
|
c@125
|
642 usage();
|
c@125
|
643 } else {
|
c@125
|
644 debug = true;
|
c@125
|
645 arg = argv[2];
|
c@125
|
646 }
|
c@125
|
647 }
|
c@125
|
648
|
c@125
|
649 string format = arg;
|
c@125
|
650
|
c@125
|
651 if (format != "capnp" && format != "json") {
|
c@125
|
652 usage();
|
c@125
|
653 }
|
c@125
|
654
|
c@138
|
655 try {
|
c@138
|
656 initFds(format == "capnp");
|
c@138
|
657 } catch (exception &e) {
|
c@138
|
658 cerr << "ERROR: " << e.what() << endl;
|
c@138
|
659 exit(1);
|
c@125
|
660 }
|
c@138
|
661
|
c@138
|
662 suspendOutput();
|
c@125
|
663
|
c@125
|
664 if (debug) {
|
c@125
|
665 cerr << myname << " " << pid << ": waiting for format: " << format << endl;
|
c@125
|
666 }
|
c@138
|
667
|
c@125
|
668 while (true) {
|
c@125
|
669
|
c@125
|
670 RequestOrResponse request;
|
c@125
|
671
|
c@125
|
672 try {
|
c@125
|
673
|
cannam@158
|
674 bool eof = false;
|
cannam@158
|
675 request = readRequest(format, eof);
|
c@125
|
676
|
cannam@158
|
677 if (eof) {
|
c@125
|
678 if (debug) {
|
c@125
|
679 cerr << myname << " " << pid << ": eof reached, exiting" << endl;
|
c@125
|
680 }
|
c@125
|
681 break;
|
c@125
|
682 }
|
c@125
|
683
|
c@125
|
684 if (debug) {
|
c@125
|
685 cerr << myname << " " << pid << ": request received, of type "
|
c@125
|
686 << int(request.type)
|
c@125
|
687 << endl;
|
c@125
|
688 }
|
cannam@158
|
689
|
cannam@158
|
690 } catch (exception &e) {
|
c@125
|
691
|
cannam@158
|
692 if (debug) {
|
cannam@158
|
693 cerr << myname << " " << pid << ": error: " << e.what() << endl;
|
cannam@158
|
694 }
|
cannam@158
|
695
|
cannam@158
|
696 writeException(format, e, request.type, request.id);
|
cannam@158
|
697
|
cannam@158
|
698 if (format == "capnp") {
|
cannam@158
|
699 // Don't try to continue; we can't recover from a
|
cannam@158
|
700 // mangled input stream. However, we can return a
|
cannam@158
|
701 // successful error code because we are reporting the
|
cannam@158
|
702 // status in our Capnp output stream instead
|
cannam@158
|
703 if (debug) {
|
cannam@158
|
704 cerr << myname << " " << pid << ": not attempting to recover from capnp parse problems, exiting" << endl;
|
cannam@158
|
705 }
|
cannam@158
|
706 exit(0);
|
cannam@158
|
707 }
|
cannam@158
|
708 }
|
cannam@158
|
709
|
cannam@158
|
710 try {
|
c@125
|
711 RequestOrResponse response = handleRequest(request, debug);
|
c@125
|
712 response.id = request.id;
|
c@125
|
713
|
c@125
|
714 if (debug) {
|
c@125
|
715 cerr << myname << " " << pid << ": request handled, writing response"
|
c@125
|
716 << endl;
|
c@125
|
717 }
|
c@125
|
718
|
c@125
|
719 writeResponse(format, response);
|
c@125
|
720
|
c@125
|
721 if (debug) {
|
c@125
|
722 cerr << myname << " " << pid << ": response written" << endl;
|
c@125
|
723 }
|
c@125
|
724
|
c@125
|
725 if (request.type == RRType::Finish) {
|
c@125
|
726 auto h = mapper.pluginToHandle(request.finishRequest.plugin);
|
c@125
|
727 if (debug) {
|
c@125
|
728 cerr << myname << " " << pid << ": deleting the plugin with handle " << h << endl;
|
c@125
|
729 }
|
c@125
|
730 mapper.removePlugin(h);
|
c@125
|
731 delete request.finishRequest.plugin;
|
c@125
|
732 }
|
c@125
|
733
|
c@138
|
734 } catch (exception &e) {
|
c@125
|
735
|
c@125
|
736 if (debug) {
|
c@125
|
737 cerr << myname << " " << pid << ": error: " << e.what() << endl;
|
c@125
|
738 }
|
c@125
|
739
|
cannam@158
|
740 writeException(format, e, request.type, request.id);
|
c@125
|
741 }
|
c@125
|
742 }
|
c@125
|
743
|
c@125
|
744 exit(0);
|
c@125
|
745 }
|