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