/* * Copyright (c) 2012 Jan Vesely * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup libhound * @addtogroup audio * @{ */ /** @file * Common USB functions. */ #include #include #include #include #include #include #include #include #include "protocol.h" #include "client.h" #include "server.h" enum ipc_methods { IPC_M_HOUND_CONTEXT_REGISTER = IPC_FIRST_USER_METHOD, IPC_M_HOUND_CONTEXT_UNREGISTER, IPC_M_HOUND_GET_LIST, IPC_M_HOUND_CONNECT, IPC_M_HOUND_DISCONNECT, IPC_M_HOUND_STREAM_ENTER, IPC_M_HOUND_STREAM_EXIT, IPC_M_HOUND_STREAM_DRAIN, }; typedef union { struct { uint16_t rate; uint8_t channels; uint8_t format; } f __attribute__((packed)); sysarg_t arg; } format_convert_t; /**** * CLIENT ****/ const char *HOUND_SERVICE = "audio/hound"; hound_sess_t *hound_service_connect(const char *service) { service_id_t id = 0; const int ret = loc_service_get_id(service, &id, IPC_FLAG_BLOCKING); if (ret != EOK) return NULL; return loc_service_connect(EXCHANGE_PARALLEL, id, IPC_FLAG_BLOCKING); } void hound_service_disconnect(hound_sess_t *sess) { if (sess) async_hangup(sess); } hound_context_id_t hound_service_register_context(hound_sess_t *sess, const char *name, bool record) { assert(sess); assert(name); ipc_call_t call; async_exch_t *exch = async_exchange_begin(sess); aid_t mid = async_send_1(exch, IPC_M_HOUND_CONTEXT_REGISTER, record, &call); int ret = mid ? EOK : EPARTY; if (ret == EOK) ret = async_data_write_start(exch, name, str_size(name)); else async_forget(mid); if (ret == EOK) async_wait_for(mid, (sysarg_t *)&ret); async_exchange_end(exch); return ret == EOK ? (hound_context_id_t)IPC_GET_ARG1(call) : ret; } int hound_service_unregister_context(hound_sess_t *sess, hound_context_id_t id) { assert(sess); async_exch_t *exch = async_exchange_begin(sess); const int ret = async_req_1_0(exch, IPC_M_HOUND_CONTEXT_UNREGISTER, id); async_exchange_end(exch); return ret; } int hound_service_get_list(hound_sess_t *sess, const char ***ids, size_t *count, int flags, const char *connection) { assert(sess); assert(ids); assert(count); if (connection && !(flags & HOUND_CONNECTED)) return EINVAL; async_exch_t *exch = async_exchange_begin(sess); if (!exch) return ENOMEM; ipc_call_t res_call; aid_t mid = async_send_3(exch, IPC_M_HOUND_GET_LIST, flags, *count, (bool)connection, &res_call); int ret = EOK; if (mid && connection) ret = async_data_write_start(exch, connection, str_size(connection)); if (ret == EOK) async_wait_for(mid, (sysarg_t*)&ret); if (ret != EOK) { async_exchange_end(exch); return ret; } unsigned name_count = IPC_GET_ARG1(res_call); /* Start receiving names */ const char **names = NULL; if (name_count) { size_t *sizes = calloc(name_count, sizeof(size_t)); names = calloc(name_count, sizeof(char *)); if (!names || !sizes) ret = ENOMEM; if (ret == EOK) ret = async_data_read_start(exch, sizes, name_count * sizeof(size_t)); for (unsigned i = 0; i < name_count && ret == EOK; ++i) { char *name = malloc(sizes[i] + 1); if (name) { bzero(name, sizes[i] + 1); ret = async_data_read_start(exch, name, sizes[i]); names[i] = name; } else { ret = ENOMEM; } } free(sizes); } async_exchange_end(exch); if (ret != EOK) { for (unsigned i = 0; i < name_count; ++i) free(names[i]); free(names); } else { *ids = names; *count = name_count; } return ret; } int hound_service_connect_source_sink(hound_sess_t *sess, const char *source, const char *sink) { assert(sess); assert(source); assert(sink); async_exch_t *exch = async_exchange_begin(sess); if (!exch) return ENOMEM; ipc_call_t call; aid_t id = async_send_0(exch, IPC_M_HOUND_CONNECT, &call); int ret = id ? EOK : EPARTY; if (ret == EOK) ret = async_data_write_start(exch, source, str_size(source)); if (ret == EOK) ret = async_data_write_start(exch, sink, str_size(sink)); async_wait_for(id, (sysarg_t*)&ret); async_exchange_end(exch); return ret; } int hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source, const char *sink) { assert(sess); async_exch_t *exch = async_exchange_begin(sess); if (!exch) return ENOMEM; ipc_call_t call; aid_t id = async_send_0(exch, IPC_M_HOUND_DISCONNECT, &call); int ret = id ? EOK : EPARTY; if (ret == EOK) ret = async_data_write_start(exch, source, str_size(source)); if (ret == EOK) ret = async_data_write_start(exch, sink, str_size(sink)); async_wait_for(id, (sysarg_t*)&ret); async_exchange_end(exch); return ENOTSUP; } int hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id, int flags, pcm_format_t format, size_t bsize) { const format_convert_t c = { .f = { .channels = format.channels, .rate = format.sampling_rate / 100, .format = format.sample_format, }}; return async_req_4_0(exch, IPC_M_HOUND_STREAM_ENTER, id, flags, c.arg, bsize); } int hound_service_stream_exit(async_exch_t *exch) { return async_req_0_0(exch, IPC_M_HOUND_STREAM_EXIT); } int hound_service_stream_drain(async_exch_t *exch) { return async_req_0_0(exch, IPC_M_HOUND_STREAM_DRAIN); } int hound_service_stream_write(async_exch_t *exch, const void *data, size_t size) { return async_data_write_start(exch, data, size); } int hound_service_stream_read(async_exch_t *exch, void *data, size_t size) { return async_data_read_start(exch, data, size); } /**** * SERVER ****/ static void hound_server_read_data(void *stream); static void hound_server_write_data(void *stream); static const hound_server_iface_t *server_iface; void hound_service_set_server_iface(const hound_server_iface_t *iface) { server_iface = iface; } void hound_connection_handler(ipc_callid_t iid, ipc_call_t *icall, void *arg) { /* Accept connection if there is a valid iface*/ if (server_iface) { async_answer_0(iid, EOK); } else { async_answer_0(iid, ENOTSUP); return; } while (1) { ipc_call_t call; ipc_callid_t callid = async_get_call(&call); switch (IPC_GET_IMETHOD(call)) { case IPC_M_HOUND_CONTEXT_REGISTER: { if (!server_iface || !server_iface->add_context) { async_answer_0(callid, ENOTSUP); break; } bool record = IPC_GET_ARG1(call); void *name; int ret = async_data_write_accept(&name, true, 0, 0, 0, 0); if (ret != EOK) { async_answer_0(callid, ret); break; } hound_context_id_t id = 0; ret = server_iface->add_context(server_iface->server, &id, name, record); if (ret != EOK) { free(name); async_answer_0(callid, ret); } else { async_answer_1(callid, EOK, id); } break; } case IPC_M_HOUND_CONTEXT_UNREGISTER: { if (!server_iface || !server_iface->rem_context) { async_answer_0(callid, ENOTSUP); break; } hound_context_id_t id = IPC_GET_ARG1(call); const int ret = server_iface->rem_context(server_iface->server, id); async_answer_0(callid, ret); break; } case IPC_M_HOUND_GET_LIST: { if (!server_iface || !server_iface->get_list) { async_answer_0(callid, ENOTSUP); break; } const char **list = NULL; const int flags = IPC_GET_ARG1(call); size_t count = IPC_GET_ARG2(call); const bool conn = IPC_GET_ARG3(call); char *conn_name = NULL; int ret = EOK; if (conn) ret = async_data_write_accept( (void**)&conn_name, true, 0, 0, 0, 0); if (ret == EOK) ret = server_iface->get_list( server_iface->server, &list, &count, conn_name, flags); free(conn_name); size_t *sizes = NULL; if (count) sizes = calloc(count, sizeof(size_t)); if (count && !sizes) ret = ENOMEM; async_answer_1(callid, ret, count); /* We are done */ if (count == 0 || ret != EOK) break; /* Prepare sizes table */ for (unsigned i = 0; i < count; ++i) sizes[i] = str_size(list[i]); /* Send sizes table */ ipc_callid_t id; if (async_data_read_receive(&id, NULL)) { ret = async_data_read_finalize(id, sizes, count * sizeof(size_t)); } free(sizes); /* Proceed to send names */ for (unsigned i = 0; i < count; ++i) { size_t size = str_size(list[i]); ipc_callid_t id; if (ret == EOK && async_data_read_receive(&id, NULL)) { ret = async_data_read_finalize(id, list[i], size); } free(list[i]); } free(list); break; } case IPC_M_HOUND_CONNECT: { if (!server_iface || !server_iface->connect) { async_answer_0(callid, ENOTSUP); break; } void *source = NULL; void *sink = NULL; int ret = async_data_write_accept(&source, true, 0, 0, 0, 0); if (ret == EOK) ret = async_data_write_accept(&sink, true, 0, 0, 0, 0); if (ret == EOK) ret = server_iface->connect( server_iface->server, source, sink); free(source); free(sink); async_answer_0(callid, ret); break; } case IPC_M_HOUND_DISCONNECT: { if (!server_iface || !server_iface->disconnect) { async_answer_0(callid, ENOTSUP); break; } void *source = NULL; void *sink = NULL; int ret = async_data_write_accept(&source, true, 0, 0, 0, 0); if (ret == EOK) ret = async_data_write_accept(&sink, true, 0, 0, 0, 0); if (ret == EOK) ret = server_iface->connect( server_iface->server, source, sink); free(source); free(sink); async_answer_0(callid, ret); break; } case IPC_M_HOUND_STREAM_ENTER: { if (!server_iface || !server_iface->is_record_context || !server_iface->add_stream || !server_iface->rem_stream) { async_answer_0(callid, ENOTSUP); break; } hound_context_id_t id = IPC_GET_ARG1(call); const int flags = IPC_GET_ARG2(call); const format_convert_t c = {.arg = IPC_GET_ARG3(call)}; const pcm_format_t f = { .sampling_rate = c.f.rate * 100, .channels = c.f.channels, .sample_format = c.f.format, }; size_t bsize = IPC_GET_ARG4(call); void *stream; int ret = server_iface->add_stream(server_iface->server, id, flags, f, bsize, &stream); if (ret != EOK) { async_answer_0(callid, ret); break; } const bool rec = server_iface->is_record_context( server_iface->server, id); if (rec) { if(server_iface->stream_data_read) { async_answer_0(callid, EOK); hound_server_write_data(stream); server_iface->rem_stream( server_iface->server, stream); } else { async_answer_0(callid, ENOTSUP); } } else { if (server_iface->stream_data_write) { async_answer_0(callid, EOK); hound_server_read_data(stream); server_iface->rem_stream( server_iface->server, stream); } else { async_answer_0(callid, ENOTSUP); } } break; } case IPC_M_HOUND_STREAM_EXIT: case IPC_M_HOUND_STREAM_DRAIN: /* Stream exit/drain is only allowed in stream context*/ async_answer_0(callid, EINVAL); break; default: async_answer_0(callid, ENOTSUP); return; } } } static void hound_server_read_data(void *stream) { ipc_callid_t callid; ipc_call_t call; size_t size = 0; int ret_answer = EOK; while (async_data_write_receive_call(&callid, &call, &size) || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) { if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) { int ret = ENOTSUP; if (server_iface->drain_stream) ret = server_iface->drain_stream(stream); async_answer_0(callid, ret); continue; } if (ret_answer != EOK) { async_answer_0(callid, ret_answer); continue; } char *buffer = malloc(size); if (!buffer) { async_answer_0(callid, ENOMEM); continue; } const int ret = async_data_write_finalize(callid, buffer, size); if (ret == EOK) { ret_answer = server_iface->stream_data_write( stream, buffer, size); } } const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT ? EOK : EINVAL; async_answer_0(callid, ret); } static void hound_server_write_data(void *stream) { ipc_callid_t callid; ipc_call_t call; size_t size = 0; int ret_answer = EOK; while (async_data_read_receive_call(&callid, &call, &size) || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) { if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) { int ret = ENOTSUP; if (server_iface->drain_stream) ret = server_iface->drain_stream(stream); async_answer_0(callid, ret); continue; } if (ret_answer != EOK) { async_answer_0(callid, ret_answer); continue; } char *buffer = malloc(size); if (!buffer) { async_answer_0(callid, ENOMEM); continue; } int ret = server_iface->stream_data_read(stream, buffer, size); if (ret == EOK) { ret_answer = async_data_read_finalize(callid, buffer, size); } } const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT ? EOK : EINVAL; async_answer_0(callid, ret); } /*** * SERVER SIDE ***/ int hound_server_register(const char *name, service_id_t *id) { if (!name || !id) return EINVAL; int ret = loc_server_register(name); if (ret != EOK) return ret; return loc_service_register(HOUND_SERVICE, id); } void hound_server_unregister(service_id_t id) { loc_service_unregister(id); } int hound_server_set_device_change_callback(dev_change_callback_t cb) { return loc_register_cat_change_cb(cb); } int hound_server_devices_iterate(device_callback_t callback) { if (!callback) return EINVAL; static bool resolved = false; static category_id_t cat_id = 0; if (!resolved) { const int ret = loc_category_get_id("audio-pcm", &cat_id, IPC_FLAG_BLOCKING); if (ret != EOK) return ret; resolved = true; } service_id_t *svcs = NULL; size_t count = 0; const int ret = loc_category_get_svcs(cat_id, &svcs, &count); if (ret != EOK) return ret; for (unsigned i = 0; i < count; ++i) { char *name = NULL; loc_service_get_name(svcs[i], &name); callback(svcs[i], name); free(name); } free(svcs); return EOK; } /** * @} */