2 * Copyright (c) 2012 Jan Vesely
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 /** @addtogroup libhound
34 * Common USB functions.
43 #include <types/common.h>
50 /** Create new context representation on the server side */
51 IPC_M_HOUND_CONTEXT_REGISTER
= IPC_FIRST_USER_METHOD
,
52 /** Release existing context representation on the server side */
53 IPC_M_HOUND_CONTEXT_UNREGISTER
,
54 /** Request list of objects */
56 /** Create new connection */
58 /** Destroy connection */
59 IPC_M_HOUND_DISCONNECT
,
60 /** Switch IPC pipe to stream mode */
61 IPC_M_HOUND_STREAM_ENTER
,
62 /** Switch IPC pipe back to general mode */
63 IPC_M_HOUND_STREAM_EXIT
,
64 /** Wait until there is no data in the stream */
65 IPC_M_HOUND_STREAM_DRAIN
,
69 /** PCM format conversion helper structure */
75 } f
__attribute__((packed
));
84 /** Well defined service name */
85 const char *HOUND_SERVICE
= "audio/hound";
88 * Start a new audio session.
89 * @param service Named service typically 'HOUND_SERVICE' constant.
90 * @return Valid session on success, NULL on failure.
92 hound_sess_t
*hound_service_connect(const char *service
)
96 loc_service_get_id(service
, &id
, IPC_FLAG_BLOCKING
);
99 return loc_service_connect(id
, INTERFACE_HOUND
, IPC_FLAG_BLOCKING
);
103 * End an existing audio session.
104 * @param sess The session.
106 void hound_service_disconnect(hound_sess_t
*sess
)
113 * Register a named application context to the audio server.
114 * @param sess Valid audio session.
115 * @param name Valid string identifier
116 * @param record True if the application context wishes to receive data.
118 * @param[out] id Return context ID.
120 * @return EOK on success, Error code on failure.
122 errno_t
hound_service_register_context(hound_sess_t
*sess
,
123 const char *name
, bool record
, hound_context_id_t
*id
)
128 async_exch_t
*exch
= async_exchange_begin(sess
);
130 async_send_1(exch
, IPC_M_HOUND_CONTEXT_REGISTER
, record
, &call
);
131 errno_t ret
= mid
? EOK
: EPARTY
;
134 ret
= async_data_write_start(exch
, name
, str_size(name
));
139 async_wait_for(mid
, &ret
);
141 async_exchange_end(exch
);
143 *id
= (hound_context_id_t
)IPC_GET_ARG1(call
);
150 * Remove application context from the server's list.
151 * @param sess Valid audio session.
152 * @param id Valid context id.
153 * @return Error code.
155 errno_t
hound_service_unregister_context(hound_sess_t
*sess
, hound_context_id_t id
)
158 async_exch_t
*exch
= async_exchange_begin(sess
);
160 async_req_1_0(exch
, IPC_M_HOUND_CONTEXT_UNREGISTER
, id
);
161 async_exchange_end(exch
);
166 * Retrieve a list of server side actors.
167 * @param[in] sess Valid audio session.
168 * @param[out] ids list of string identifiers.
169 * @param[out] count Number of elements int the @p ids list.
170 * @param[in] flags list requirements.
171 * @param[in] connection name of target actor. Used only if the list should
172 * contain connected actors.
173 * @retval Error code.
175 errno_t
hound_service_get_list(hound_sess_t
*sess
, char ***ids
, size_t *count
,
176 int flags
, const char *connection
)
182 if (connection
&& !(flags
& HOUND_CONNECTED
))
185 async_exch_t
*exch
= async_exchange_begin(sess
);
190 aid_t mid
= async_send_3(exch
, IPC_M_HOUND_GET_LIST
, flags
, *count
,
191 connection
!= NULL
, &res_call
);
194 if (mid
&& connection
)
195 ret
= async_data_write_start(exch
, connection
,
196 str_size(connection
));
199 async_wait_for(mid
, &ret
);
202 async_exchange_end(exch
);
205 unsigned name_count
= IPC_GET_ARG1(res_call
);
207 /* Start receiving names */
210 size_t *sizes
= calloc(name_count
, sizeof(size_t));
211 names
= calloc(name_count
, sizeof(char *));
212 if (!names
|| !sizes
)
216 ret
= async_data_read_start(exch
, sizes
,
217 name_count
* sizeof(size_t));
218 for (unsigned i
= 0; i
< name_count
&& ret
== EOK
; ++i
) {
219 char *name
= malloc(sizes
[i
] + 1);
221 memset(name
, 0, sizes
[i
] + 1);
222 ret
= async_data_read_start(exch
, name
, sizes
[i
]);
230 async_exchange_end(exch
);
232 for (unsigned i
= 0; i
< name_count
; ++i
)
243 * Create a new connection between a source and a sink.
244 * @param sess Valid audio session.
245 * @param source Source name, valid string.
246 * @param sink Sink name, valid string.
247 * @return Error code.
249 errno_t
hound_service_connect_source_sink(hound_sess_t
*sess
, const char *source
,
256 async_exch_t
*exch
= async_exchange_begin(sess
);
260 aid_t id
= async_send_0(exch
, IPC_M_HOUND_CONNECT
, &call
);
261 errno_t ret
= id
? EOK
: EPARTY
;
263 ret
= async_data_write_start(exch
, source
, str_size(source
));
265 ret
= async_data_write_start(exch
, sink
, str_size(sink
));
266 async_wait_for(id
, &ret
);
267 async_exchange_end(exch
);
272 * Destroy an existing connection between a source and a sink.
273 * @param sess Valid audio session.
274 * @param source Source name, valid string.
275 * @param sink Sink name, valid string.
276 * @return Error code.
278 errno_t
hound_service_disconnect_source_sink(hound_sess_t
*sess
, const char *source
,
282 async_exch_t
*exch
= async_exchange_begin(sess
);
286 aid_t id
= async_send_0(exch
, IPC_M_HOUND_DISCONNECT
, &call
);
287 errno_t ret
= id
? EOK
: EPARTY
;
289 ret
= async_data_write_start(exch
, source
, str_size(source
));
291 ret
= async_data_write_start(exch
, sink
, str_size(sink
));
292 async_wait_for(id
, &ret
);
293 async_exchange_end(exch
);
298 * Switch IPC exchange to a STREAM mode.
299 * @param exch IPC exchange.
300 * @param id context id this stream should be associated with
301 * @param flags set stream properties
302 * @param format format of the new stream.
303 * @param bsize size of the server side buffer.
304 * @return Error code.
306 errno_t
hound_service_stream_enter(async_exch_t
*exch
, hound_context_id_t id
,
307 int flags
, pcm_format_t format
, size_t bsize
)
309 const format_convert_t c
= { .f
= {
310 .channels
= format
.channels
,
311 .rate
= format
.sampling_rate
/ 100,
312 .format
= format
.sample_format
,
314 return async_req_4_0(exch
, IPC_M_HOUND_STREAM_ENTER
, id
, flags
,
319 * Destroy existing stream and return IPC exchange to general mode.
320 * @param exch IPC exchange.
321 * @return Error code.
323 errno_t
hound_service_stream_exit(async_exch_t
*exch
)
325 return async_req_0_0(exch
, IPC_M_HOUND_STREAM_EXIT
);
329 * Wait until the server side buffer is empty.
330 * @param exch IPC exchange.
331 * @return Error code.
333 errno_t
hound_service_stream_drain(async_exch_t
*exch
)
335 return async_req_0_0(exch
, IPC_M_HOUND_STREAM_DRAIN
);
339 * Write audio data to a stream.
340 * @param exch IPC exchange in STREAM MODE.
341 * @param data Audio data buffer.
342 * @size size of the buffer
343 * @return Error code.
345 errno_t
hound_service_stream_write(async_exch_t
*exch
, const void *data
, size_t size
)
347 return async_data_write_start(exch
, data
, size
);
351 * Read data from a stream.
352 * @param exch IPC exchange in STREAM MODE.
353 * @param data Audio data buffer.
354 * @size size of the buffer
355 * @return Error code.
357 errno_t
hound_service_stream_read(async_exch_t
*exch
, void *data
, size_t size
)
359 return async_data_read_start(exch
, data
, size
);
366 static void hound_server_read_data(void *stream
);
367 static void hound_server_write_data(void *stream
);
368 static const hound_server_iface_t
*server_iface
;
371 * Set hound server interface implementation.
372 * @param iface Initialized Hound server interface.
374 void hound_service_set_server_iface(const hound_server_iface_t
*iface
)
376 server_iface
= iface
;
380 * Server side implementation of the hound protocol. IPC connection handler.
381 * @param iid initial call id
382 * @param icall pointer to initial call structure.
383 * @param arg (unused)
385 void hound_connection_handler(ipc_callid_t iid
, ipc_call_t
*icall
, void *arg
)
387 /* Accept connection if there is a valid iface*/
389 async_answer_0(iid
, EOK
);
391 async_answer_0(iid
, ENOTSUP
);
397 ipc_callid_t callid
= async_get_call(&call
);
398 switch (IPC_GET_IMETHOD(call
)) {
399 case IPC_M_HOUND_CONTEXT_REGISTER
: {
400 /* check interface functions */
401 if (!server_iface
|| !server_iface
->add_context
) {
402 async_answer_0(callid
, ENOTSUP
);
405 bool record
= IPC_GET_ARG1(call
);
408 /* Get context name */
410 async_data_write_accept(&name
, true, 0, 0, 0, 0);
412 async_answer_0(callid
, ret
);
415 hound_context_id_t id
= 0;
416 ret
= server_iface
->add_context(server_iface
->server
,
418 /** new context should create a copy */
421 async_answer_0(callid
, ret
);
423 async_answer_1(callid
, EOK
, id
);
427 case IPC_M_HOUND_CONTEXT_UNREGISTER
: {
428 /* check interface functions */
429 if (!server_iface
|| !server_iface
->rem_context
) {
430 async_answer_0(callid
, ENOTSUP
);
434 /* get id, 1st param */
435 hound_context_id_t id
= IPC_GET_ARG1(call
);
437 server_iface
->rem_context(server_iface
->server
, id
);
438 async_answer_0(callid
, ret
);
441 case IPC_M_HOUND_GET_LIST
: {
442 /* check interface functions */
443 if (!server_iface
|| !server_iface
->get_list
) {
444 async_answer_0(callid
, ENOTSUP
);
449 const int flags
= IPC_GET_ARG1(call
);
450 size_t count
= IPC_GET_ARG2(call
);
451 const bool conn
= IPC_GET_ARG3(call
);
452 char *conn_name
= NULL
;
455 /* get connected actor name if provided */
457 ret
= async_data_write_accept(
458 (void**)&conn_name
, true, 0, 0, 0, 0);
461 ret
= server_iface
->get_list(
462 server_iface
->server
, &list
, &count
,
466 /* Alloc string sizes array */
467 size_t *sizes
= NULL
;
469 sizes
= calloc(count
, sizeof(size_t));
472 async_answer_1(callid
, ret
, count
);
475 if (count
== 0 || ret
!= EOK
)
478 /* Prepare sizes table */
479 for (unsigned i
= 0; i
< count
; ++i
)
480 sizes
[i
] = str_size(list
[i
]);
482 /* Send sizes table */
484 if (async_data_read_receive(&id
, NULL
)) {
485 ret
= async_data_read_finalize(id
, sizes
,
486 count
* sizeof(size_t));
490 /* Proceed to send names */
491 for (unsigned i
= 0; i
< count
; ++i
) {
492 size_t size
= str_size(list
[i
]);
495 async_data_read_receive(&id
, NULL
)) {
496 ret
= async_data_read_finalize(id
,
504 case IPC_M_HOUND_CONNECT
: {
505 /* check interface functions */
506 if (!server_iface
|| !server_iface
->connect
) {
507 async_answer_0(callid
, ENOTSUP
);
514 /* read source name */
516 async_data_write_accept(&source
, true, 0, 0, 0, 0);
519 ret
= async_data_write_accept(&sink
,
523 ret
= server_iface
->connect(
524 server_iface
->server
, source
, sink
);
527 async_answer_0(callid
, ret
);
530 case IPC_M_HOUND_DISCONNECT
: {
531 /* check interface functions */
532 if (!server_iface
|| !server_iface
->disconnect
) {
533 async_answer_0(callid
, ENOTSUP
);
540 /* read source name */
542 async_data_write_accept(&source
, true, 0, 0, 0, 0);
545 ret
= async_data_write_accept(&sink
,
548 ret
= server_iface
->connect(
549 server_iface
->server
, source
, sink
);
552 async_answer_0(callid
, ret
);
555 case IPC_M_HOUND_STREAM_ENTER
: {
556 /* check interface functions */
557 if (!server_iface
|| !server_iface
->is_record_context
558 || !server_iface
->add_stream
559 || !server_iface
->rem_stream
) {
560 async_answer_0(callid
, ENOTSUP
);
565 hound_context_id_t id
= IPC_GET_ARG1(call
);
566 const int flags
= IPC_GET_ARG2(call
);
567 const format_convert_t c
= {.arg
= IPC_GET_ARG3(call
)};
568 const pcm_format_t f
= {
569 .sampling_rate
= c
.f
.rate
* 100,
570 .channels
= c
.f
.channels
,
571 .sample_format
= c
.f
.format
,
573 size_t bsize
= IPC_GET_ARG4(call
);
576 errno_t ret
= server_iface
->add_stream(server_iface
->server
,
577 id
, flags
, f
, bsize
, &stream
);
579 async_answer_0(callid
, ret
);
582 const bool rec
= server_iface
->is_record_context(
583 server_iface
->server
, id
);
585 if(server_iface
->stream_data_read
) {
586 async_answer_0(callid
, EOK
);
587 /* start answering read calls */
588 hound_server_write_data(stream
);
589 server_iface
->rem_stream(
590 server_iface
->server
, stream
);
592 async_answer_0(callid
, ENOTSUP
);
595 if (server_iface
->stream_data_write
) {
596 async_answer_0(callid
, EOK
);
597 /* accept write calls */
598 hound_server_read_data(stream
);
599 server_iface
->rem_stream(
600 server_iface
->server
, stream
);
602 async_answer_0(callid
, ENOTSUP
);
607 case IPC_M_HOUND_STREAM_EXIT
:
608 case IPC_M_HOUND_STREAM_DRAIN
:
609 /* Stream exit/drain is only allowed in stream context*/
610 async_answer_0(callid
, EINVAL
);
613 async_answer_0(callid
, ENOTSUP
);
620 * Read data and push it to the stream.
621 * @param stream target stream, will push data there.
623 static void hound_server_read_data(void *stream
)
628 errno_t ret_answer
= EOK
;
629 /* accept data write or drain */
630 while (async_data_write_receive_call(&callid
, &call
, &size
)
631 || (IPC_GET_IMETHOD(call
) == IPC_M_HOUND_STREAM_DRAIN
)) {
632 /* check drain first */
633 if (IPC_GET_IMETHOD(call
) == IPC_M_HOUND_STREAM_DRAIN
) {
634 errno_t ret
= ENOTSUP
;
635 if (server_iface
->drain_stream
)
636 ret
= server_iface
->drain_stream(stream
);
637 async_answer_0(callid
, ret
);
641 /* there was an error last time */
642 if (ret_answer
!= EOK
) {
643 async_answer_0(callid
, ret_answer
);
647 char *buffer
= malloc(size
);
649 async_answer_0(callid
, ENOMEM
);
652 const errno_t ret
= async_data_write_finalize(callid
, buffer
, size
);
654 /* push data to stream */
655 ret_answer
= server_iface
->stream_data_write(
656 stream
, buffer
, size
);
659 const errno_t ret
= IPC_GET_IMETHOD(call
) == IPC_M_HOUND_STREAM_EXIT
662 async_answer_0(callid
, ret
);
666 * Accept reads and pull data from the stream.
667 * @param stream target stream, will pull data from there.
669 static void hound_server_write_data(void *stream
)
675 errno_t ret_answer
= EOK
;
676 /* accept data read and drain */
677 while (async_data_read_receive_call(&callid
, &call
, &size
)
678 || (IPC_GET_IMETHOD(call
) == IPC_M_HOUND_STREAM_DRAIN
)) {
679 /* drain does not make much sense but it is allowed */
680 if (IPC_GET_IMETHOD(call
) == IPC_M_HOUND_STREAM_DRAIN
) {
681 errno_t ret
= ENOTSUP
;
682 if (server_iface
->drain_stream
)
683 ret
= server_iface
->drain_stream(stream
);
684 async_answer_0(callid
, ret
);
687 /* there was an error last time */
688 if (ret_answer
!= EOK
) {
689 async_answer_0(callid
, ret_answer
);
692 char *buffer
= malloc(size
);
694 async_answer_0(callid
, ENOMEM
);
697 errno_t ret
= server_iface
->stream_data_read(stream
, buffer
, size
);
700 async_data_read_finalize(callid
, buffer
, size
);
703 const errno_t ret
= IPC_GET_IMETHOD(call
) == IPC_M_HOUND_STREAM_EXIT
706 async_answer_0(callid
, ret
);
715 * Register new hound service to the location service.
716 * @param[in] name server name
717 * @param[out] id assigned service id.
718 * @return Error code.
720 errno_t
hound_server_register(const char *name
, service_id_t
*id
)
725 errno_t ret
= loc_server_register(name
);
729 return loc_service_register(HOUND_SERVICE
, id
);
733 * Unregister server from the location service.
734 * @param id previously assigned service id.
736 void hound_server_unregister(service_id_t id
)
738 loc_service_unregister(id
);
742 * Set callback on device category change event.
743 * @param cb Callback function.
744 * @return Error code.
746 errno_t
hound_server_set_device_change_callback(dev_change_callback_t cb
)
748 return loc_register_cat_change_cb(cb
);
752 * Walk through all device in the audio-pcm category.
753 * @param callback Function to call on every device.
754 * @return Error code.
756 errno_t
hound_server_devices_iterate(device_callback_t callback
)
760 static bool resolved
= false;
761 static category_id_t cat_id
= 0;
764 const errno_t ret
= loc_category_get_id("audio-pcm", &cat_id
,
771 service_id_t
*svcs
= NULL
;
773 const errno_t ret
= loc_category_get_svcs(cat_id
, &svcs
, &count
);
777 for (unsigned i
= 0; i
< count
; ++i
) {
779 loc_service_get_name(svcs
[i
], &name
);
780 callback(svcs
[i
], name
);