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 {
|
cannam@272
|
81 cout << "2.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"
|
cannam@279
|
94 " -d, --debug: also print debug information to stderr\n"
|
cannam@279
|
95 " -v, --version: print version number to stdout and exit\n"
|
cannam@279
|
96 " -h, --help: 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 {
|
cannam@262
|
158 fflush(stdout);
|
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);
|
cannam@293
|
464
|
cannam@293
|
465 if (!response.loadResponse.plugin) {
|
cannam@293
|
466 throw runtime_error("unable to load plugin");
|
c@125
|
467 }
|
cannam@293
|
468
|
cannam@293
|
469 mapper.addPlugin(response.loadResponse.plugin);
|
cannam@293
|
470 if (debug) {
|
cannam@293
|
471 cerr << "piper-vamp-server " << pid
|
cannam@293
|
472 << ": loaded plugin, handle = "
|
cannam@293
|
473 << mapper.pluginToHandle(response.loadResponse.plugin)
|
cannam@293
|
474 << endl;
|
cannam@293
|
475 }
|
cannam@293
|
476 response.success = true;
|
c@125
|
477 break;
|
c@125
|
478
|
c@125
|
479 case RRType::Configure:
|
c@125
|
480 {
|
c@125
|
481 auto &creq = request.configurationRequest;
|
cannam@158
|
482 if (!creq.plugin) {
|
cannam@158
|
483 throw runtime_error("unknown plugin handle supplied to configure");
|
cannam@158
|
484 }
|
cannam@158
|
485
|
c@125
|
486 auto h = mapper.pluginToHandle(creq.plugin);
|
c@125
|
487 if (mapper.isConfigured(h)) {
|
c@125
|
488 throw runtime_error("plugin has already been configured");
|
c@125
|
489 }
|
c@125
|
490
|
cannam@185
|
491 if (creq.configuration.framing.stepSize == 0 ||
|
cannam@185
|
492 creq.configuration.framing.blockSize == 0) {
|
cannam@185
|
493 throw runtime_error("step and block size must be non-zero");
|
cannam@185
|
494 }
|
cannam@185
|
495
|
c@125
|
496 response.configurationResponse = LoaderRequests().configurePlugin(creq);
|
cannam@293
|
497
|
cannam@293
|
498 if (response.configurationResponse.outputs.empty()) {
|
cannam@293
|
499 throw runtime_error("plugin failed to initialise");
|
cannam@293
|
500 }
|
c@125
|
501
|
cannam@293
|
502 mapper.markConfigured
|
cannam@293
|
503 (h,
|
cannam@293
|
504 creq.configuration.channelCount,
|
cannam@293
|
505 response.configurationResponse.framing.blockSize);
|
cannam@293
|
506 response.success = true;
|
c@125
|
507 break;
|
c@125
|
508 }
|
c@125
|
509
|
c@125
|
510 case RRType::Process:
|
c@125
|
511 {
|
c@125
|
512 auto &preq = request.processRequest;
|
cannam@158
|
513 if (!preq.plugin) {
|
cannam@158
|
514 throw runtime_error("unknown plugin handle supplied to process");
|
cannam@158
|
515 }
|
cannam@158
|
516
|
c@125
|
517 auto h = mapper.pluginToHandle(preq.plugin);
|
c@125
|
518 if (!mapper.isConfigured(h)) {
|
c@125
|
519 throw runtime_error("plugin has not been configured");
|
c@125
|
520 }
|
c@125
|
521
|
c@125
|
522 int channels = int(preq.inputBuffers.size());
|
c@125
|
523 if (channels != mapper.getChannelCount(h)) {
|
c@125
|
524 throw runtime_error("wrong number of channels supplied to process");
|
c@125
|
525 }
|
c@125
|
526
|
c@125
|
527 const float **fbuffers = new const float *[channels];
|
cannam@271
|
528
|
cannam@271
|
529 bool frequencyDomain =
|
cannam@271
|
530 (preq.plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain);
|
cannam@271
|
531 int blockSize = mapper.getBlockSize(h);
|
cannam@271
|
532 int inputBufferSize;
|
cannam@271
|
533 if (frequencyDomain) {
|
cannam@271
|
534 inputBufferSize = 2 * (blockSize / 2) + 2;
|
cannam@271
|
535 } else {
|
cannam@271
|
536 inputBufferSize = blockSize;
|
cannam@271
|
537 }
|
cannam@271
|
538
|
c@125
|
539 for (int i = 0; i < channels; ++i) {
|
cannam@271
|
540 if (int(preq.inputBuffers[i].size()) != inputBufferSize) {
|
cannam@186
|
541 ostringstream os;
|
cannam@271
|
542 os << "wrong buffer size passed to process call as "
|
cannam@271
|
543 << (frequencyDomain ? "frequency" : "time")
|
cannam@271
|
544 << "-domain input on channel " << i << " with block size "
|
cannam@271
|
545 << blockSize << " (expected " << inputBufferSize
|
cannam@271
|
546 << " values, obtained " << preq.inputBuffers[i].size()
|
cannam@271
|
547 << ")" << ends;
|
c@125
|
548 delete[] fbuffers;
|
cannam@186
|
549 throw runtime_error(os.str());
|
c@125
|
550 }
|
c@125
|
551 fbuffers[i] = preq.inputBuffers[i].data();
|
c@125
|
552 }
|
c@125
|
553
|
c@125
|
554 response.processResponse.plugin = preq.plugin;
|
c@125
|
555 response.processResponse.features =
|
c@125
|
556 preq.plugin->process(fbuffers, preq.timestamp);
|
c@125
|
557 response.success = true;
|
c@125
|
558
|
c@125
|
559 delete[] fbuffers;
|
c@125
|
560 break;
|
c@125
|
561 }
|
c@125
|
562
|
c@125
|
563 case RRType::Finish:
|
c@125
|
564 {
|
c@125
|
565 auto &freq = request.finishRequest;
|
cannam@158
|
566 if (!freq.plugin) {
|
cannam@158
|
567 throw runtime_error("unknown plugin handle supplied to finish");
|
cannam@158
|
568 }
|
cannam@158
|
569
|
c@125
|
570 response.finishResponse.plugin = freq.plugin;
|
c@125
|
571
|
c@125
|
572 auto h = mapper.pluginToHandle(freq.plugin);
|
c@125
|
573 // Finish can be called (to unload the plugin) even if the
|
c@125
|
574 // plugin has never been configured or used. But we want to
|
c@125
|
575 // make sure we call getRemainingFeatures only if we have
|
c@125
|
576 // actually configured the plugin.
|
c@125
|
577 if (mapper.isConfigured(h)) {
|
c@125
|
578 response.finishResponse.features = freq.plugin->getRemainingFeatures();
|
c@125
|
579 }
|
c@125
|
580
|
c@125
|
581 // We do not delete the plugin here -- we need it in the
|
c@125
|
582 // mapper when converting the features. It gets deleted in the
|
c@125
|
583 // calling function.
|
c@125
|
584 response.success = true;
|
c@125
|
585 break;
|
c@125
|
586 }
|
c@125
|
587
|
c@125
|
588 case RRType::NotValid:
|
c@125
|
589 break;
|
c@125
|
590 }
|
c@125
|
591
|
c@125
|
592 return response;
|
c@125
|
593 }
|
c@125
|
594
|
c@125
|
595 RequestOrResponse
|
cannam@158
|
596 readRequest(string format, bool &eof)
|
c@125
|
597 {
|
c@125
|
598 if (format == "capnp") {
|
cannam@158
|
599 return readRequestCapnp(eof);
|
c@125
|
600 } else if (format == "json") {
|
c@125
|
601 string err;
|
cannam@158
|
602 auto result = readRequestJson(err, eof);
|
c@125
|
603 if (err != "") throw runtime_error(err);
|
c@125
|
604 else return result;
|
c@125
|
605 } else {
|
c@125
|
606 throw runtime_error("unknown input format \"" + format + "\"");
|
c@125
|
607 }
|
c@125
|
608 }
|
c@125
|
609
|
c@125
|
610 void
|
c@125
|
611 writeResponse(string format, RequestOrResponse &rr)
|
c@125
|
612 {
|
c@138
|
613 resumeOutput();
|
c@125
|
614 if (format == "capnp") {
|
c@125
|
615 writeResponseCapnp(rr);
|
c@125
|
616 } else if (format == "json") {
|
c@125
|
617 writeResponseJson(rr, false);
|
c@125
|
618 } else {
|
c@125
|
619 throw runtime_error("unknown output format \"" + format + "\"");
|
c@125
|
620 }
|
c@138
|
621 suspendOutput();
|
c@125
|
622 }
|
c@125
|
623
|
c@125
|
624 void
|
cannam@158
|
625 writeException(string format, const exception &e, RRType type, RequestOrResponse::RpcId id)
|
c@125
|
626 {
|
c@138
|
627 resumeOutput();
|
c@125
|
628 if (format == "capnp") {
|
cannam@158
|
629 writeExceptionCapnp(e, type, id);
|
c@125
|
630 } else if (format == "json") {
|
cannam@158
|
631 writeExceptionJson(e, type, id);
|
c@125
|
632 } else {
|
c@125
|
633 throw runtime_error("unknown output format \"" + format + "\"");
|
c@125
|
634 }
|
c@138
|
635 suspendOutput();
|
c@125
|
636 }
|
c@125
|
637
|
c@125
|
638 int main(int argc, char **argv)
|
c@125
|
639 {
|
c@125
|
640 if (argc != 2 && argc != 3) {
|
c@125
|
641 usage();
|
c@125
|
642 }
|
c@125
|
643
|
c@125
|
644 bool debug = false;
|
c@125
|
645
|
c@125
|
646 string arg = argv[1];
|
cannam@279
|
647 if (arg == "-h" || arg == "--help") {
|
c@125
|
648 if (argc == 2) {
|
c@125
|
649 usage(true);
|
c@125
|
650 } else {
|
c@125
|
651 usage();
|
c@125
|
652 }
|
cannam@279
|
653 } else if (arg == "-v" || arg == "--version") {
|
c@125
|
654 if (argc == 2) {
|
c@125
|
655 version();
|
c@125
|
656 } else {
|
c@125
|
657 usage();
|
c@125
|
658 }
|
cannam@279
|
659 } else if (arg == "-d" || arg == "--debug") {
|
c@125
|
660 if (argc == 2) {
|
c@125
|
661 usage();
|
c@125
|
662 } else {
|
c@125
|
663 debug = true;
|
c@125
|
664 arg = argv[2];
|
c@125
|
665 }
|
c@125
|
666 }
|
c@125
|
667
|
c@125
|
668 string format = arg;
|
c@125
|
669
|
c@125
|
670 if (format != "capnp" && format != "json") {
|
c@125
|
671 usage();
|
c@125
|
672 }
|
c@125
|
673
|
c@138
|
674 try {
|
c@138
|
675 initFds(format == "capnp");
|
c@138
|
676 } catch (exception &e) {
|
c@138
|
677 cerr << "ERROR: " << e.what() << endl;
|
c@138
|
678 exit(1);
|
c@125
|
679 }
|
c@138
|
680
|
c@138
|
681 suspendOutput();
|
c@125
|
682
|
c@125
|
683 if (debug) {
|
c@125
|
684 cerr << myname << " " << pid << ": waiting for format: " << format << endl;
|
cannam@228
|
685 if (format == "json") {
|
cannam@281
|
686 cerr << myname << " " << pid << ": to test the server, try {\"method\": \"list\"}" << endl;
|
cannam@228
|
687 }
|
c@125
|
688 }
|
c@138
|
689
|
c@125
|
690 while (true) {
|
c@125
|
691
|
c@125
|
692 RequestOrResponse request;
|
c@125
|
693
|
c@125
|
694 try {
|
c@125
|
695
|
cannam@158
|
696 bool eof = false;
|
cannam@158
|
697 request = readRequest(format, eof);
|
c@125
|
698
|
cannam@158
|
699 if (eof) {
|
c@125
|
700 if (debug) {
|
c@125
|
701 cerr << myname << " " << pid << ": eof reached, exiting" << endl;
|
c@125
|
702 }
|
c@125
|
703 break;
|
c@125
|
704 }
|
c@125
|
705
|
c@125
|
706 if (debug) {
|
c@125
|
707 cerr << myname << " " << pid << ": request received, of type "
|
c@125
|
708 << int(request.type)
|
c@125
|
709 << endl;
|
c@125
|
710 }
|
cannam@158
|
711
|
cannam@158
|
712 } catch (exception &e) {
|
c@125
|
713
|
cannam@158
|
714 if (debug) {
|
cannam@158
|
715 cerr << myname << " " << pid << ": error: " << e.what() << endl;
|
cannam@158
|
716 }
|
cannam@158
|
717
|
cannam@158
|
718 writeException(format, e, request.type, request.id);
|
cannam@158
|
719
|
cannam@158
|
720 if (format == "capnp") {
|
cannam@158
|
721 // Don't try to continue; we can't recover from a
|
cannam@158
|
722 // mangled input stream. However, we can return a
|
cannam@158
|
723 // successful error code because we are reporting the
|
cannam@158
|
724 // status in our Capnp output stream instead
|
cannam@158
|
725 if (debug) {
|
cannam@158
|
726 cerr << myname << " " << pid << ": not attempting to recover from capnp parse problems, exiting" << endl;
|
cannam@158
|
727 }
|
cannam@158
|
728 exit(0);
|
cannam@158
|
729 }
|
cannam@158
|
730 }
|
cannam@158
|
731
|
cannam@158
|
732 try {
|
c@125
|
733 RequestOrResponse response = handleRequest(request, debug);
|
c@125
|
734 response.id = request.id;
|
c@125
|
735
|
c@125
|
736 if (debug) {
|
c@125
|
737 cerr << myname << " " << pid << ": request handled, writing response"
|
c@125
|
738 << endl;
|
c@125
|
739 }
|
c@125
|
740
|
c@125
|
741 writeResponse(format, response);
|
c@125
|
742
|
c@125
|
743 if (debug) {
|
c@125
|
744 cerr << myname << " " << pid << ": response written" << endl;
|
c@125
|
745 }
|
c@125
|
746
|
c@125
|
747 if (request.type == RRType::Finish) {
|
c@125
|
748 auto h = mapper.pluginToHandle(request.finishRequest.plugin);
|
c@125
|
749 if (debug) {
|
c@125
|
750 cerr << myname << " " << pid << ": deleting the plugin with handle " << h << endl;
|
c@125
|
751 }
|
c@125
|
752 mapper.removePlugin(h);
|
c@125
|
753 delete request.finishRequest.plugin;
|
c@125
|
754 }
|
c@125
|
755
|
c@138
|
756 } catch (exception &e) {
|
c@125
|
757
|
c@125
|
758 if (debug) {
|
c@125
|
759 cerr << myname << " " << pid << ": error: " << e.what() << endl;
|
c@125
|
760 }
|
c@125
|
761
|
cannam@158
|
762 writeException(format, e, request.type, request.id);
|
c@125
|
763 }
|
c@125
|
764 }
|
c@125
|
765
|
c@125
|
766 exit(0);
|
c@125
|
767 }
|