/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* Basic playback flow:
* cras_client_create - Create new structure and set to defaults.
* cras_client_connect - Connect client to server - sets up server_fd to
* communicate with the audio server. After the client connects, the server
* will send back a message containing the client id.
* cras_client_add_stream - Add a playback or capture stream. Creates a
* client_stream struct and send a file descriptor to server. That file
* descriptor and aud_fd are a pair created from socketpair().
* client_connected - The server will send a connected message to indicate that
* the client should start receving audio events from aud_fd. This message
* also specifies the shared memory region to use to share audio samples.
* This region will be shmat'd.
* running - Once the connections are established, the client will listen for
* requests on aud_fd and fill the shm region with the requested number of
* samples. This happens in the aud_cb specified in the stream parameters.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* For ppoll() */
#endif
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/eventfd.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/un.h>
#include <syslog.h>
#include <unistd.h>
#include "cras_client.h"
#include "cras_config.h"
#include "cras_file_wait.h"
#include "cras_messages.h"
#include "cras_observer_ops.h"
#include "cras_shm.h"
#include "cras_types.h"
#include "cras_util.h"
#include "utlist.h"
static const size_t MAX_CMD_MSG_LEN = 256;
static const size_t SERVER_SHUTDOWN_TIMEOUT_US = 500000;
static const size_t SERVER_CONNECT_TIMEOUT_MS = 1000;
static const size_t HOTWORD_FRAME_RATE = 16000;
static const size_t HOTWORD_BLOCK_SIZE = 320;
/* Commands sent from the user to the running client. */
enum {
CLIENT_STOP,
CLIENT_ADD_STREAM,
CLIENT_REMOVE_STREAM,
CLIENT_SET_STREAM_VOLUME_SCALER,
CLIENT_SERVER_CONNECT,
CLIENT_SERVER_CONNECT_ASYNC,
};
struct command_msg {
unsigned len;
unsigned msg_id;
cras_stream_id_t stream_id;
};
struct set_stream_volume_command_message {
struct command_msg header;
float volume_scaler;
};
/* Adds a stream to the client.
* stream - The stream to add.
* stream_id_out - Filled with the stream id of the new stream.
* dev_idx - Index of the device to attach the newly created stream.
* NO_DEVICE means not to pin the stream to a device.
*/
struct add_stream_command_message {
struct command_msg header;
struct client_stream *stream;
cras_stream_id_t *stream_id_out;
uint32_t dev_idx;
};
/* Commands send from a running stream to the client. */
enum {
CLIENT_STREAM_EOF,
};
struct stream_msg {
unsigned msg_id;
cras_stream_id_t stream_id;
};
enum CRAS_THREAD_STATE {
CRAS_THREAD_STOP, /* Isn't (shouldn't be) running. */
CRAS_THREAD_WARMUP, /* Is started, but not fully functional: waiting
* for resources to be ready for example. */
CRAS_THREAD_RUNNING, /* Is running and fully functional. */
};
/* Manage information for a thread. */
struct thread_state {
pthread_t tid;
enum CRAS_THREAD_STATE state;
};
/* Parameters used when setting up a capture or playback stream. See comment
* above cras_client_create_stream_params in the header for descriptions. */
struct cras_stream_params {
enum CRAS_STREAM_DIRECTION direction;
size_t buffer_frames;
size_t cb_threshold;
enum CRAS_STREAM_TYPE stream_type;
uint32_t flags;
uint64_t effects;
void *user_data;
cras_playback_cb_t aud_cb;
cras_unified_cb_t unified_cb;
cras_error_cb_t err_cb;
struct cras_audio_format format;
};
/* Represents an attached audio stream.
* id - Unique stream identifier.
* aud_fd - After server connects audio messages come in here.
* direction - playback, capture, both, or loopback (see CRAS_STREAM_DIRECTION).
* flags - Currently not used.
* volume_scaler - Amount to scale the stream by, 0.0 to 1.0.
* tid - Thread id of the audio thread spawned for this stream.
* running - Audio thread runs while this is non-zero.
* wake_fds - Pipe to wake the audio thread.
* client - The client this stream is attached to.
* config - Audio stream configuration.
* capture_shm - Shared memory used to exchange audio samples with the server.
* play_shm - Shared memory used to exchange audio samples with the server.
* prev, next - Form a linked list of streams attached to a client.
*/
struct client_stream {
cras_stream_id_t id;
int aud_fd; /* audio messages from server come in here. */
enum CRAS_STREAM_DIRECTION direction;
uint32_t flags;
float volume_scaler;
struct thread_state thread;
int wake_fds[2]; /* Pipe to wake the thread */
struct cras_client *client;
struct cras_stream_params *config;
struct cras_audio_shm capture_shm;
int capture_shm_size;
struct cras_audio_shm play_shm;
int play_shm_size;
struct client_stream *prev, *next;
};
/* State of the socket. */
typedef enum cras_socket_state {
CRAS_SOCKET_STATE_DISCONNECTED,
/* Not connected. Also used to cleanup the current connection
* before restarting the connection attempt. */
CRAS_SOCKET_STATE_WAIT_FOR_SOCKET,
/* Waiting for the socket file to exist. Socket file existence
* is monitored using cras_file_wait. */
CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE,
/* Waiting for the socket to have something at the other end. */
CRAS_SOCKET_STATE_FIRST_MESSAGE,
/* Waiting for the first messages from the server and set our
* client ID. */
CRAS_SOCKET_STATE_CONNECTED,
/* The socket is connected and working. */
CRAS_SOCKET_STATE_ERROR_DELAY,
/* There was an error during one of the above states. Sleep for
* a bit before continuing. If this state could not be initiated
* then we move to the DISCONNECTED state and notify via the
* connection callback. */
} cras_socket_state_t;
/* Represents a client used to communicate with the audio server.
* id - Unique identifier for this client, negative until connected.
* server_fd - Incoming messages from server.
* server_fd_state - State of the server's socket.
* server_event_fd - Eventfd to wait on until a connection is established.
* stream_fds - Pipe for attached streams.
* command_fds - Pipe for user commands to thread.
* command_reply_fds - Pipe for acking/nacking command messages from thread.
* sock_file - Server communication socket file.
* sock_file_wait - Structure used to monitor existence of the socket file.
* sock_file_exists - Set to true when the socket file exists.
* running - The client thread will run while this is non zero.
* next_stream_id - ID to give the next stream.
* stream_start_cond - Condition used during stream startup.
* stream_start_lock - Lock used during stream startup.
* tid - Thread ID of the client thread started by "cras_client_run_thread".
* last_command_result - Passes back the result of the last user command.
* streams - Linked list of streams attached to this client.
* server_state - RO shared memory region holding server state.
* debug_info_callback - Function to call when debug info is received.
* get_hotword_models_cb_t - Function to call when hotword models info is ready.
* server_err_cb - Function to call when failed to read messages from server.
* server_err_user_arg - User argument for server_err_cb.
* server_connection_cb - Function to called when a connection state changes.
* server_connection_user_arg - User argument for server_connection_cb.
* thread_priority_cb - Function to call for setting audio thread priority.
* observer_ops - Functions to call when system state changes.
* observer_context - Context passed to client in state change callbacks.
*/
struct cras_client {
int id;
int server_fd;
cras_socket_state_t server_fd_state;
int server_event_fd;
int stream_fds[2];
int command_fds[2];
int command_reply_fds[2];
const char *sock_file;
struct cras_file_wait *sock_file_wait;
bool sock_file_exists;
struct thread_state thread;
cras_stream_id_t next_stream_id;
pthread_cond_t stream_start_cond;
pthread_mutex_t stream_start_lock;
int last_command_result;
struct client_stream *streams;
const struct cras_server_state *server_state;
void (*debug_info_callback)(struct cras_client *);
get_hotword_models_cb_t get_hotword_models_cb;
cras_server_error_cb_t server_err_cb;
cras_connection_status_cb_t server_connection_cb;
void *server_connection_user_arg;
cras_thread_priority_cb_t thread_priority_cb;
struct cras_observer_ops observer_ops;
void *observer_context;
};
/*
* Holds the client pointer plus internal book keeping.
*
* client - The client
* server_state_rwlock - lock to make the client's server_state thread-safe.
*/
struct client_int {
struct cras_client client;
pthread_rwlock_t server_state_rwlock;
};
#define to_client_int(cptr) \
((struct client_int *)((char *)cptr - offsetof(struct client_int, client)))
/*
* Holds the hotword stream format, params, and ID used when waiting for a
* hotword. The structure is created by cras_client_enable_hotword_callback and
* destroyed by cras_client_disable_hotword_callback.
*/
struct cras_hotword_handle {
struct cras_audio_format *format;
struct cras_stream_params *params;
cras_stream_id_t stream_id;
cras_hotword_trigger_cb_t trigger_cb;
cras_hotword_error_cb_t err_cb;
void *user_data;
};
/*
* Local Helpers
*/
static int client_thread_rm_stream(struct cras_client *client,
cras_stream_id_t stream_id);
static int handle_message_from_server(struct cras_client *client);
static int reregister_notifications(struct cras_client *client);
/*
* Unlock the server_state_rwlock if lock_rc is 0.
*
* Args:
* client - The CRAS client pointer.
* lock_rc - The result of server_state_rdlock or
* server_state_wrlock.
*/
static void server_state_unlock(const struct cras_client *client,
int lock_rc)
{
struct client_int *client_int;
if (!client)
return;
client_int = to_client_int(client);
if (lock_rc == 0)
pthread_rwlock_unlock(&client_int->server_state_rwlock);
}
/*
* Lock the server_state_rwlock for reading.
*
* Also checks that the server_state pointer is valid.
*
* Args:
* client - The CRAS client pointer.
* Returns:
* 0 for success, positive error code on error.
* Returns EINVAL if the server state pointer is NULL.
*/
static int server_state_rdlock(const struct cras_client *client)
{
struct client_int *client_int;
int lock_rc;
if (!client)
return EINVAL;
client_int = to_client_int(client);
lock_rc = pthread_rwlock_rdlock(&client_int->server_state_rwlock);
if (lock_rc != 0)
return lock_rc;
if (!client->server_state) {
pthread_rwlock_unlock(&client_int->server_state_rwlock);
return EINVAL;
}
return 0;
}
/*
* Lock the server_state_rwlock for writing.
*
* Args:
* client - The CRAS client pointer.
* Returns:
* 0 for success, positive error code on error.
*/
static int server_state_wrlock(const struct cras_client *client)
{
struct client_int *client_int;
if (!client)
return EINVAL;
client_int = to_client_int(client);
return pthread_rwlock_wrlock(&client_int->server_state_rwlock);
}
/* Get the stream pointer from a stream id. */
static struct client_stream *stream_from_id(const struct cras_client *client,
unsigned int id)
{
struct client_stream *out;
DL_SEARCH_SCALAR(client->streams, out, id, id);
return out;
}
/*
* Fill a pollfd structure with the current server fd and events.
*/
void server_fill_pollfd(const struct cras_client *client,
struct pollfd *poll_fd)
{
int events = 0;
poll_fd->fd = client->server_fd;
switch (client->server_fd_state) {
case CRAS_SOCKET_STATE_DISCONNECTED:
break;
case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
case CRAS_SOCKET_STATE_FIRST_MESSAGE:
case CRAS_SOCKET_STATE_CONNECTED:
case CRAS_SOCKET_STATE_ERROR_DELAY:
events = POLLIN;
break;
case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
events = POLLOUT;
break;
}
poll_fd->events = events;
poll_fd->revents = 0;
}
/*
* Change the server_fd_state.
*/
static void server_fd_move_to_state(struct cras_client *client,
cras_socket_state_t state)
{
if (state == client->server_fd_state)
return;
client->server_fd_state = state;
}
/*
* Action to take when in state ERROR_DELAY.
*
* In this state we want to sleep for a few seconds before retrying the
* connection to the audio server.
*
* If server_fd is negative: create a timer and setup server_fd with the
* timer's fd. If server_fd is not negative and there is input, then assume
* that the timer has expired, and restart the connection by moving to
* WAIT_FOR_SOCKET state.
*/
static int error_delay_next_action(struct cras_client *client,
int poll_revents)
{
int rc;
struct itimerspec timeout;
if (client->server_fd == -1) {
client->server_fd = timerfd_create(
CLOCK_MONOTONIC,
TFD_NONBLOCK|TFD_CLOEXEC);
if (client->server_fd == -1) {
rc = -errno;
syslog(LOG_ERR,
"cras_client: Could not create timerfd: %s",
strerror(-rc));
return rc;
}
/* Setup a relative timeout of 2 seconds. */
memset(&timeout, 0, sizeof(timeout));
timeout.it_value.tv_sec = 2;
rc = timerfd_settime(client->server_fd, 0, &timeout, NULL);
if (rc != 0) {
rc = -errno;
syslog(LOG_ERR,
"cras_client: Could not set timeout: %s",
strerror(-rc));
return rc;
}
return 0;
} else if ((poll_revents & POLLIN) == 0) {
return 0;
}
/* Move to the next state: close the timer fd first. */
close(client->server_fd);
client->server_fd = -1;
server_fd_move_to_state(client, CRAS_SOCKET_STATE_WAIT_FOR_SOCKET);
return 0;
}
/*
* Action to take when in WAIT_FOR_SOCKET state.
*
* In this state we are waiting for the socket file to exist. The existence of
* the socket file is continually monitored using the cras_file_wait structure
* and a separate fd. When the sock_file_exists boolean is modified, the state
* machine is invoked.
*
* If the socket file exists, then we move to the WAIT_FOR_WRITABLE state.
*/
static void wait_for_socket_next_action(struct cras_client *client)
{
if (client->sock_file_exists)
server_fd_move_to_state(
client, CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE);
}
/*
* Action to take when in WAIT_FOR_WRITABLE state.
*
* In this state we are initiating a connection the server and waiting for the
* server to ready for incoming messages.
*
* Create the socket to the server, and wait while a connect request results in
* -EINPROGRESS. Otherwise, we assume that the socket file will be deleted by
* the server and the server_fd_state will be changed in
* sock_file_wait_dispatch().
*/
static int wait_for_writable_next_action(struct cras_client *client,
int poll_revents)
{
int rc;
struct sockaddr_un address;
if (client->server_fd == -1) {
client->server_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
if (client->server_fd < 0) {
rc = -errno;
syslog(LOG_ERR, "cras_client: server socket failed: %s",
strerror(-rc));
return rc;
}
}
else if ((poll_revents & POLLOUT) == 0) {
return 0;
}
/* We make the file descriptor non-blocking when we do connect(), so we
* don't block indefinitely. */
cras_make_fd_nonblocking(client->server_fd);
memset(&address, 0, sizeof(struct sockaddr_un));
address.sun_family = AF_UNIX;
strcpy(address.sun_path, client->sock_file);
rc = connect(client->server_fd, (struct sockaddr *)&address,
sizeof(struct sockaddr_un));
if (rc != 0) {
rc = -errno;
/* For -EINPROGRESS, we wait for POLLOUT on the server_fd.
* Otherwise CRAS is not running and we assume that the socket
* file will be deleted and recreated. Notification of that will
* happen via the sock_file_wait_dispatch(). */
if (rc == -ECONNREFUSED) {
/* CRAS is not running, don't log this error and just
* stay in this state waiting sock_file_wait_dispatch()
* to move the state machine. */
close(client->server_fd);
client->server_fd = -1;
}
else if (rc != -EINPROGRESS) {
syslog(LOG_ERR,
"cras_client: server connect failed: %s",
strerror(-rc));
return rc;
}
return 0;
}
cras_make_fd_blocking(client->server_fd);
server_fd_move_to_state(client, CRAS_SOCKET_STATE_FIRST_MESSAGE);
return 0;
}
/*
* Action to take when transitioning to the CONNECTED state.
*/
static int connect_transition_action(struct cras_client *client)
{
eventfd_t event_value;
int rc;
rc = reregister_notifications(client);
if (rc < 0)
return rc;
server_fd_move_to_state(client, CRAS_SOCKET_STATE_CONNECTED);
/* Notify anyone waiting on this state change that we're
* connected. */
eventfd_read(client->server_event_fd, &event_value);
eventfd_write(client->server_event_fd, 1);
if (client->server_connection_cb)
client->server_connection_cb(
client, CRAS_CONN_STATUS_CONNECTED,
client->server_connection_user_arg);
return 0;
}
/*
* Action to take when in the FIRST_MESSAGE state.
*
* We are waiting for the first message from the server. When our client ID has
* been set, then we can move to the CONNECTED state.
*/
static int first_message_next_action(struct cras_client *client,
int poll_revents)
{
int rc;
if (client->server_fd < 0)
return -EINVAL;
if ((poll_revents & POLLIN) == 0)
return 0;
rc = handle_message_from_server(client);
if (rc < 0) {
syslog(LOG_ERR, "handle first message: %s", strerror(-rc));
} else if (client->id >= 0) {
rc = connect_transition_action(client);
} else {
syslog(LOG_ERR, "did not get ID after first message!");
rc = -EINVAL;
}
return rc;
}
/*
* Play nice and shutdown the server socket.
*/
static inline int shutdown_and_close_socket(int sockfd)
{
int rc;
uint8_t buffer[CRAS_CLIENT_MAX_MSG_SIZE];
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = SERVER_SHUTDOWN_TIMEOUT_US;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
rc = shutdown(sockfd, SHUT_WR);
if (rc < 0)
return rc;
/* Wait until the socket is closed by the peer. */
for (;;) {
rc = recv(sockfd, buffer, sizeof(buffer), 0);
if (rc <= 0)
break;
}
return close(sockfd);
}
/*
* Action to take when disconnecting from the server.
*
* Clean up the server socket, and the server_state pointer. Move to the next
* logical state.
*/
static void disconnect_transition_action(struct cras_client *client, bool force)
{
eventfd_t event_value;
cras_socket_state_t old_state = client->server_fd_state;
struct client_stream *s;
int lock_rc;
/* Stop all playing streams.
* TODO(muirj): Pause and resume streams. */
DL_FOREACH(client->streams, s) {
s->config->err_cb(client, s->id, -ENOTCONN,
s->config->user_data);
client_thread_rm_stream(client, s->id);
}
/* Clean up the server_state pointer. */
lock_rc = server_state_wrlock(client);
if (client->server_state) {
munmap((void *)client->server_state,
sizeof(*client->server_state));
client->server_state = NULL;
}
server_state_unlock(client, lock_rc);
/* Our ID is unknown now. */
client->id = -1;
/* Clean up the server fd. */
if (client->server_fd >= 0) {
if (!force)
shutdown_and_close_socket(client->server_fd);
else
close(client->server_fd);
client->server_fd = -1;
}
/* Reset the server_event_fd value to 0 (and cause subsequent threads
* waiting on the connection to wait). */
eventfd_read(client->server_event_fd, &event_value);
switch (old_state) {
case CRAS_SOCKET_STATE_DISCONNECTED:
/* Do nothing: already disconnected. */
break;
case CRAS_SOCKET_STATE_ERROR_DELAY:
/* We're disconnected and there was a failure to setup
* automatic reconnection, so call the server error
* callback now. */
server_fd_move_to_state(
client, CRAS_SOCKET_STATE_DISCONNECTED);
if (client->server_connection_cb)
client->server_connection_cb(
client, CRAS_CONN_STATUS_FAILED,
client->server_connection_user_arg);
else if (client->server_err_cb)
client->server_err_cb(
client, client->server_connection_user_arg);
break;
case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
case CRAS_SOCKET_STATE_FIRST_MESSAGE:
/* We are running this state transition while a connection is
* in progress for an error case. When there is no error, we
* come into this function in the DISCONNECTED state. */
server_fd_move_to_state(
client, CRAS_SOCKET_STATE_ERROR_DELAY);
break;
case CRAS_SOCKET_STATE_CONNECTED:
/* Disconnected from CRAS (for an error), wait for the socket
* file to be (re)created. */
server_fd_move_to_state(
client, CRAS_SOCKET_STATE_WAIT_FOR_SOCKET);
/* Notify the caller that we aren't connected anymore. */
if (client->server_connection_cb)
client->server_connection_cb(
client, CRAS_CONN_STATUS_DISCONNECTED,
client->server_connection_user_arg);
break;
}
}
static int server_fd_dispatch(struct cras_client *client, int poll_revents)
{
int rc = 0;
cras_socket_state_t old_state;
if ((poll_revents & POLLHUP) != 0) {
/* Error or disconnect: cleanup and make a state change now. */
disconnect_transition_action(client, true);
}
old_state = client->server_fd_state;
switch (client->server_fd_state) {
case CRAS_SOCKET_STATE_DISCONNECTED:
/* Assume that we've taken the necessary actions. */
return -ENOTCONN;
case CRAS_SOCKET_STATE_ERROR_DELAY:
rc = error_delay_next_action(client, poll_revents);
break;
case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
wait_for_socket_next_action(client);
break;
case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
rc = wait_for_writable_next_action(client, poll_revents);
break;
case CRAS_SOCKET_STATE_FIRST_MESSAGE:
rc = first_message_next_action(client, poll_revents);
break;
case CRAS_SOCKET_STATE_CONNECTED:
if ((poll_revents & POLLIN) != 0)
rc = handle_message_from_server(client);
break;
}
if (rc != 0) {
/* If there is an error, then start-over. */
rc = server_fd_dispatch(client, POLLHUP);
} else if (old_state != client->server_fd_state) {
/* There was a state change, process the new state now. */
rc = server_fd_dispatch(client, 0);
}
return rc;
}
/*
* Start connecting to the server if we aren't already.
*/
static int server_connect(struct cras_client *client)
{
if (client->server_fd_state != CRAS_SOCKET_STATE_DISCONNECTED)
return 0;
/* Start waiting for the server socket to exist. */
server_fd_move_to_state(client, CRAS_SOCKET_STATE_WAIT_FOR_SOCKET);
return server_fd_dispatch(client, 0);
}
/*
* Disconnect from the server if we haven't already.
*/
static void server_disconnect(struct cras_client *client)
{
if (client->server_fd_state == CRAS_SOCKET_STATE_DISCONNECTED)
return;
/* Set the disconnected state first so that the disconnect
* transition doesn't move the server state to ERROR_DELAY. */
server_fd_move_to_state(client, CRAS_SOCKET_STATE_DISCONNECTED);
disconnect_transition_action(client, false);
}
/*
* Called when something happens to the socket file.
*/
static void sock_file_wait_callback(void *context, cras_file_wait_event_t event,
const char *filename)
{
struct cras_client *client = (struct cras_client *)context;
switch (event) {
case CRAS_FILE_WAIT_EVENT_CREATED:
client->sock_file_exists = 1;
switch (client->server_fd_state) {
case CRAS_SOCKET_STATE_DISCONNECTED:
case CRAS_SOCKET_STATE_ERROR_DELAY:
case CRAS_SOCKET_STATE_FIRST_MESSAGE:
case CRAS_SOCKET_STATE_CONNECTED:
break;
case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
/* The socket file exists. Tell the server state
* machine. */
server_fd_dispatch(client, 0);
break;
}
break;
case CRAS_FILE_WAIT_EVENT_DELETED:
client->sock_file_exists = 0;
switch (client->server_fd_state) {
case CRAS_SOCKET_STATE_DISCONNECTED:
break;
case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
case CRAS_SOCKET_STATE_ERROR_DELAY:
case CRAS_SOCKET_STATE_FIRST_MESSAGE:
case CRAS_SOCKET_STATE_CONNECTED:
/* Restart the connection process. */
server_disconnect(client);
server_connect(client);
break;
}
break;
case CRAS_FILE_WAIT_EVENT_NONE:
break;
}
}
/*
* Service the sock_file_wait's fd.
*
* If the socket file is deleted, then cause a disconnect from the server.
* Otherwise, start a reconnect depending on the server_fd_state.
*/
static int sock_file_wait_dispatch(struct cras_client *client,
int poll_revents)
{
int rc;
if ((poll_revents & POLLIN) == 0)
return 0;
rc = cras_file_wait_dispatch(client->sock_file_wait);
if (rc == -EAGAIN || rc == -EWOULDBLOCK)
rc = 0;
else if (rc != 0)
syslog(LOG_ERR, "cras_file_wait_dispatch: %s", strerror(-rc));
return rc;
}
/*
* Waits until we have heard back from the server so that we know we are
* connected.
*
* The connected success/failure message is always the first message the server
* sends. Return non zero if client is connected to the server. A return code
* of zero means that the client is not connected to the server.
*/
static int check_server_connected_wait(struct cras_client *client,
struct timespec *timeout)
{
int rc = 0;
struct pollfd poll_fd;
poll_fd.fd = client->server_event_fd;
poll_fd.events = POLLIN;
poll_fd.revents = 0;
/* The server_event_fd is only read and written by the functions
* that connect to the server. When a connection is established the
* eventfd has a value of 1 and cras_poll will return immediately
* with 1. When there is no connection to the server, then this
* function waits until the timeout has expired or a non-zero value
* is written to the server_event_fd. */
while (rc == 0)
rc = cras_poll(&poll_fd, 1, timeout, NULL);
return rc > 0;
}
/* Returns non-zero if the thread is running (not stopped). */
static inline int thread_is_running(struct thread_state *thread)
{
return thread->state != CRAS_THREAD_STOP;
}
/*
* Opens the server socket and connects to it.
* Args:
* client - Client pointer created with cras_client_create().
* timeout - Connection timeout.
* Returns:
* 0 for success, negative error code on failure.
*/
static int connect_to_server(struct cras_client *client,
struct timespec *timeout,
bool use_command_thread)
{
int rc;
struct pollfd poll_fd[2];
struct timespec connected_timeout;
if (!client)
return -EINVAL;
if (thread_is_running(&client->thread) && use_command_thread) {
rc = cras_client_connect_async(client);
if (rc == 0) {
rc = check_server_connected_wait(client, timeout);
return rc ? 0 : -ESHUTDOWN;
}
}
connected_timeout.tv_sec = 0;
connected_timeout.tv_nsec = 0;
if (check_server_connected_wait(client, &connected_timeout))
return 0;
poll_fd[0].fd = cras_file_wait_get_fd(client->sock_file_wait);
poll_fd[0].events = POLLIN;
rc = server_connect(client);
while(rc == 0) {
// Wait until we've connected or until there is a timeout.
// Meanwhile handle incoming actions on our fds.
server_fill_pollfd(client, &(poll_fd[1]));
rc = cras_poll(poll_fd, 2, timeout, NULL);
if (rc <= 0)
continue;
if (poll_fd[0].revents) {
rc = sock_file_wait_dispatch(
client, poll_fd[0].revents);
continue;
}
if (poll_fd[1].revents) {
rc = server_fd_dispatch(client, poll_fd[1].revents);
if (rc == 0 &&
client->server_fd_state ==
CRAS_SOCKET_STATE_CONNECTED)
break;
}
}
if (rc != 0)
syslog(LOG_ERR, "cras_client: Connect server failed: %s",
strerror(-rc));
return rc;
}
static int connect_to_server_wait_retry(struct cras_client *client,
int timeout_ms,
bool use_command_thread)
{
struct timespec timeout_value;
struct timespec *timeout;
if (timeout_ms < 0) {
timeout = NULL;
} else {
timeout = &timeout_value;
ms_to_timespec(timeout_ms, timeout);
}
/* If connected, wait for the first message from the server
* indicating it's ready. */
return connect_to_server(client, timeout, use_command_thread);
}
/*
* Tries to connect to the server. Waits for the initial message from the
* server. This will happen near instantaneously if the server is already
* running.
*/
static int connect_to_server_wait(struct cras_client *client,
bool use_command_thread)
{
return connect_to_server_wait_retry(
client, SERVER_CONNECT_TIMEOUT_MS, use_command_thread);
}
/*
* Audio thread.
*/
/* Sends a message from the stream to the client to indicate an error.
* If the running stream encounters an error, then it must tell the client
* to stop running it.
*/
static int send_stream_message(const struct client_stream *stream,
unsigned msg_id)
{
int res;
struct stream_msg msg;
msg.stream_id = stream->id;
msg.msg_id = msg_id;
res = write(stream->client->stream_fds[1], &msg, sizeof(msg));
if (res != sizeof(msg))
return -EPIPE;
return 0;
}
/* Blocks until there is data to be read from the read_fd or until woken by an
* incoming "poke" on wake_fd. Up to "len" bytes are read into "buf". */
static int read_with_wake_fd(int wake_fd, int read_fd, uint8_t *buf, size_t len)
{
struct pollfd pollfds[2];
int nread = 0;
int nfds = 1;
int rc;
char tmp;
pollfds[0].fd = wake_fd;
pollfds[0].events = POLLIN;
if (read_fd >= 0) {
nfds++;
pollfds[1].fd = read_fd;
pollfds[1].events = POLLIN;
}
rc = poll(pollfds, nfds, -1);
if (rc < 0)
return rc;
if (read_fd >= 0 && pollfds[1].revents & POLLIN) {
nread = read(read_fd, buf, len);
if (nread != (int)len)
return -EIO;
}
if (pollfds[0].revents & POLLIN) {
rc = read(wake_fd, &tmp, 1);
if (rc < 0)
return rc;
}
return nread;
}
/* Check the availability and configures a capture buffer.
* Args:
* stream - The input stream to configure buffer for.
* captured_frames - To be filled with the pointer to the beginning of
* captured buffer.
* num_frames - Number of captured frames.
* Returns:
* Number of frames available in captured_frames.
*/
static unsigned int config_capture_buf(struct client_stream *stream,
uint8_t **captured_frames,
unsigned int num_frames)
{
/* Always return the beginning of the read buffer because Chrome expects
* so. */
*captured_frames = cras_shm_get_read_buffer_base(&stream->capture_shm);
/* Don't ask for more frames than the client desires. */
if (stream->flags & BULK_AUDIO_OK)
num_frames = MIN(num_frames, stream->config->buffer_frames);
else
num_frames = MIN(num_frames, stream->config->cb_threshold);
/* If shm readable frames is less than client requests, that means
* overrun has happened in server side. Don't send partial corrupted
* buffer to client. */
if (cras_shm_get_curr_read_frames(&stream->capture_shm) < num_frames)
return 0;
return num_frames;
}
static void complete_capture_read_current(struct client_stream *stream,
unsigned int num_frames)
{
cras_shm_buffer_read_current(&stream->capture_shm, num_frames);
}
static int send_capture_reply(struct client_stream *stream,
unsigned int frames,
int err)
{
struct audio_message aud_msg;
int rc;
if (!cras_stream_uses_input_hw(stream->direction))
return 0;
aud_msg.id = AUDIO_MESSAGE_DATA_CAPTURED;
aud_msg.frames = frames;
aud_msg.error = err;
rc = write(stream->aud_fd, &aud_msg, sizeof(aud_msg));
if (rc != sizeof(aud_msg))
return -EPIPE;
return 0;
}
/* For capture streams this handles the message signalling that data is ready to
* be passed to the user of this stream. Calls the audio callback with the new
* samples, and mark them as read.
* Args:
* stream - The stream the message was received for.
* num_frames - The number of captured frames.
* Returns:
* 0, unless there is a fatal error or the client declares enod of file.
*/
static int handle_capture_data_ready(struct client_stream *stream,
unsigned int num_frames)
{
int frames;
struct cras_stream_params *config;
uint8_t *captured_frames;
struct timespec ts;
int rc = 0;
config = stream->config;
/* If this message is for an output stream, log error and drop it. */
if (!cras_stream_has_input(stream->direction)) {
syslog(LOG_ERR, "cras_client: Play data to input\n");
return 0;
}
num_frames = config_capture_buf(stream, &captured_frames, num_frames);
if (num_frames == 0)
return 0;
cras_timespec_to_timespec(&ts, &stream->capture_shm.area->ts);
if (config->unified_cb)
frames = config->unified_cb(stream->client,
stream->id,
captured_frames,
NULL,
num_frames,
&ts,
NULL,
config->user_data);
else
frames = config->aud_cb(stream->client,
stream->id,
captured_frames,
num_frames,
&ts,
config->user_data);
if (frames < 0) {
send_stream_message(stream, CLIENT_STREAM_EOF);
rc = frames;
goto reply_captured;
}
if (frames == 0)
return 0;
complete_capture_read_current(stream, frames);
reply_captured:
return send_capture_reply(stream, frames, rc);
}
/* Notifies the server that "frames" samples have been written. */
static int send_playback_reply(struct client_stream *stream,
unsigned int frames,
int error)
{
struct audio_message aud_msg;
int rc;
if (!cras_stream_uses_output_hw(stream->direction))
return 0;
aud_msg.id = AUDIO_MESSAGE_DATA_READY;
aud_msg.frames = frames;
aud_msg.error = error;
rc = write(stream->aud_fd, &aud_msg, sizeof(aud_msg));
if (rc != sizeof(aud_msg))
return -EPIPE;
return 0;
}
/* For playback streams when current buffer is empty, this handles the request
* for more samples by calling the audio callback for the thread, and signaling
* the server that the samples have been written. */
static int handle_playback_request(struct client_stream *stream,
unsigned int num_frames)
{
uint8_t *buf;
int frames;
int rc = 0;
struct cras_stream_params *config;
struct cras_audio_shm *shm = &stream->play_shm;
struct timespec ts;
config = stream->config;
/* If this message is for an input stream, log error and drop it. */
if (stream->direction != CRAS_STREAM_OUTPUT) {
syslog(LOG_ERR, "cras_client: Record data from output\n");
return 0;
}
buf = cras_shm_get_write_buffer_base(&stream->play_shm);
/* Limit the amount of frames to the configured amount. */
num_frames = MIN(num_frames, config->cb_threshold);
cras_timespec_to_timespec(&ts, &shm->area->ts);
/* Get samples from the user */
if (config->unified_cb)
frames = config->unified_cb(stream->client,
stream->id,
NULL,
buf,
num_frames,
NULL,
&ts,
config->user_data);
else
frames = config->aud_cb(stream->client,
stream->id,
buf,
num_frames,
&ts,
config->user_data);
if (frames < 0) {
send_stream_message(stream, CLIENT_STREAM_EOF);
rc = frames;
goto reply_written;
}
cras_shm_buffer_written_start(shm, frames);
reply_written:
/* Signal server that data is ready, or that an error has occurred. */
rc = send_playback_reply(stream, frames, rc);
return rc;
}
static void audio_thread_set_priority(struct client_stream *stream)
{
/* Use provided callback to set priority if available. */
if (stream->client->thread_priority_cb) {
stream->client->thread_priority_cb(stream->client);
return;
}
/* Try to get RT scheduling, if that fails try to set the nice value. */
if (cras_set_rt_scheduling(CRAS_CLIENT_RT_THREAD_PRIORITY) ||
cras_set_thread_priority(CRAS_CLIENT_RT_THREAD_PRIORITY))
cras_set_nice_level(CRAS_CLIENT_NICENESS_LEVEL);
}
/* Listens to the audio socket for messages from the server indicating that
* the stream needs to be serviced. One of these runs per stream. */
static void *audio_thread(void *arg)
{
struct client_stream *stream = (struct client_stream *)arg;
int thread_terminated = 0;
struct audio_message aud_msg;
int aud_fd;
int num_read;
if (arg == NULL)
return (void *)-EIO;
audio_thread_set_priority(stream);
/* Notify the control thread that we've started. */
pthread_mutex_lock(&stream->client->stream_start_lock);
pthread_cond_broadcast(&stream->client->stream_start_cond);
pthread_mutex_unlock(&stream->client->stream_start_lock);
while (thread_is_running(&stream->thread) && !thread_terminated) {
/* While we are warming up, aud_fd may not be valid and some
* shared memory resources may not yet be available. */
aud_fd = (stream->thread.state == CRAS_THREAD_WARMUP) ?
-1 : stream->aud_fd;
num_read = read_with_wake_fd(stream->wake_fds[0],
aud_fd,
(uint8_t *)&aud_msg,
sizeof(aud_msg));
if (num_read < 0)
return (void *)-EIO;
if (num_read == 0)
continue;
switch (aud_msg.id) {
case AUDIO_MESSAGE_DATA_READY:
thread_terminated = handle_capture_data_ready(
stream,
aud_msg.frames);
break;
case AUDIO_MESSAGE_REQUEST_DATA:
thread_terminated = handle_playback_request(
stream,
aud_msg.frames);
break;
default:
break;
}
}
return NULL;
}
/* Pokes the audio thread so that it can notice if it has been terminated. */
static int wake_aud_thread(struct client_stream *stream)
{
int rc;
rc = write(stream->wake_fds[1], &rc, 1);
if (rc != 1)
return rc;
return 0;
}
/* Stop the audio thread for the given stream.
* Args:
* stream - Stream for which to stop the audio thread.
* join - When non-zero, attempt to join the audio thread (wait for it to
* complete).
*/
static void stop_aud_thread(struct client_stream *stream, int join)
{
if (thread_is_running(&stream->thread)) {
stream->thread.state = CRAS_THREAD_STOP;
wake_aud_thread(stream);
if (join)
pthread_join(stream->thread.tid, NULL);
}
if (stream->wake_fds[0] >= 0) {
close(stream->wake_fds[0]);
close(stream->wake_fds[1]);
stream->wake_fds[0] = -1;
}
}
/* Start the audio thread for this stream.
* Returns when the thread has started and is waiting.
* Args:
* stream - The stream that needs an audio thread.
* Returns:
* 0 for success, or a negative error code.
*/
static int start_aud_thread(struct client_stream *stream)
{
int rc;
struct timespec future;
rc = pipe(stream->wake_fds);
if (rc < 0) {
rc = -errno;
syslog(LOG_ERR, "cras_client: pipe: %s", strerror(-rc));
return rc;
}
stream->thread.state = CRAS_THREAD_WARMUP;
pthread_mutex_lock(&stream->client->stream_start_lock);
rc = pthread_create(&stream->thread.tid, NULL, audio_thread, stream);
if (rc) {
pthread_mutex_unlock(&stream->client->stream_start_lock);
syslog(LOG_ERR,
"cras_client: Couldn't create audio stream: %s",
strerror(rc));
stream->thread.state = CRAS_THREAD_STOP;
stop_aud_thread(stream, 0);
return -rc;
}
clock_gettime(CLOCK_REALTIME, &future);
future.tv_sec += 2; /* Wait up to two seconds. */
rc = pthread_cond_timedwait(&stream->client->stream_start_cond,
&stream->client->stream_start_lock, &future);
pthread_mutex_unlock(&stream->client->stream_start_lock);
if (rc != 0) {
/* Something is very wrong: try to cancel the thread and don't
* wait for it. */
syslog(LOG_ERR, "cras_client: Client thread not responding: %s",
strerror(rc));
stop_aud_thread(stream, 0);
return -rc;
}
return 0;
}
/*
* Client thread.
*/
/* Gets the update_count of the server state shm region. */
static inline
unsigned begin_server_state_read(const struct cras_server_state *state)
{
unsigned count;
/* Version will be odd when the server is writing. */
while ((count = *(volatile unsigned *)&state->update_count) & 1)
sched_yield();
__sync_synchronize();
return count;
}
/* Checks if the update count of the server state shm region has changed from
* count. Returns 0 if the count still matches.
*/
static inline
int end_server_state_read(const struct cras_server_state *state, unsigned count)
{
__sync_synchronize();
if (count != *(volatile unsigned *)&state->update_count)
return -EAGAIN;
return 0;
}
/* Gets the shared memory region used to share audio data with the server. */
static int config_shm(struct cras_audio_shm *shm, int shm_fd, size_t size)
{
shm->area = (struct cras_audio_shm_area *)mmap(
NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
shm_fd, 0);
if (shm->area == (struct cras_audio_shm_area *)-1) {
syslog(LOG_ERR,
"cras_client: mmap failed to map shm for stream.");
return errno;
}
/* Copy server shm config locally. */
cras_shm_copy_shared_config(shm);
return 0;
}
/* Release shm areas if references to them are held. */
static void free_shm(struct client_stream *stream)
{
if (stream->capture_shm.area) {
munmap(stream->capture_shm.area, stream->capture_shm_size);
}
if (stream->play_shm.area) {
munmap(stream->play_shm.area, stream->play_shm_size);
}
stream->capture_shm.area = NULL;
stream->play_shm.area = NULL;
}
/* Handles the stream connected message from the server. Check if we need a
* format converter, configure the shared memory region, and start the audio
* thread that will handle requests from the server. */
static int stream_connected(struct client_stream *stream,
const struct cras_client_stream_connected *msg,
const int stream_fds[2], const unsigned int num_fds)
{
int rc;
struct cras_audio_format mfmt;
if (msg->err || num_fds != 2) {
syslog(LOG_ERR, "cras_client: Error Setting up stream %d\n",
msg->err);
rc = msg->err;
goto err_ret;
}
unpack_cras_audio_format(&mfmt, &msg->format);
if (cras_stream_has_input(stream->direction)) {
rc = config_shm(&stream->capture_shm,
stream_fds[0],
msg->shm_max_size);
if (rc < 0) {
syslog(LOG_ERR,
"cras_client: Error configuring capture shm");
goto err_ret;
}
stream->capture_shm_size = msg->shm_max_size;
}
if (cras_stream_uses_output_hw(stream->direction)) {
rc = config_shm(&stream->play_shm,
stream_fds[1],
msg->shm_max_size);
if (rc < 0) {
syslog(LOG_ERR,
"cras_client: Error configuring playback shm");
goto err_ret;
}
stream->play_shm_size = msg->shm_max_size;
cras_shm_set_volume_scaler(&stream->play_shm,
stream->volume_scaler);
}
stream->thread.state = CRAS_THREAD_RUNNING;
wake_aud_thread(stream);
close(stream_fds[0]);
close(stream_fds[1]);
return 0;
err_ret:
stop_aud_thread(stream, 1);
close(stream_fds[0]);
close(stream_fds[1]);
free_shm(stream);
return rc;
}
static int send_connect_message(struct cras_client *client,
struct client_stream *stream,
uint32_t dev_idx)
{
int rc;
struct cras_connect_message serv_msg;
int sock[2] = {-1, -1};
/* Create a socket pair for the server to notify of audio events. */
rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
if (rc != 0) {
rc = -errno;
syslog(LOG_ERR, "cras_client: socketpair: %s", strerror(-rc));
goto fail;
}
cras_fill_connect_message(&serv_msg,
stream->config->direction,
stream->id,
stream->config->stream_type,
stream->config->buffer_frames,
stream->config->cb_threshold,
stream->flags,
stream->config->effects,
stream->config->format,
dev_idx);
rc = cras_send_with_fds(client->server_fd, &serv_msg, sizeof(serv_msg),
&sock[1], 1);
if (rc != sizeof(serv_msg)) {
rc = EIO;
syslog(LOG_ERR,
"cras_client: add_stream: Send server message failed.");
goto fail;
}
stream->aud_fd = sock[0];
close(sock[1]);
return 0;
fail:
if (sock[0] != -1)
close(sock[0]);
if (sock[1] != -1)
close(sock[1]);
return rc;
}
/* Adds a stream to a running client. Checks to make sure that the client is
* attached, waits if it isn't. The stream is prepared on the main thread and
* passed here. */
static int client_thread_add_stream(struct cras_client *client,
struct client_stream *stream,
cras_stream_id_t *stream_id_out,
uint32_t dev_idx)
{
int rc;
cras_stream_id_t new_id;
struct client_stream *out;
/* Find the hotword device index. */
if ((stream->flags & HOTWORD_STREAM) == HOTWORD_STREAM &&
dev_idx == NO_DEVICE) {
int hotword_idx;
hotword_idx = cras_client_get_first_dev_type_idx(client,
CRAS_NODE_TYPE_HOTWORD, CRAS_STREAM_INPUT);
if (hotword_idx < 0) {
syslog(LOG_ERR,
"cras_client: add_stream: Finding hotword dev");
return hotword_idx;
}
dev_idx = hotword_idx;
}
/* Find an available stream id. */
do {
new_id = cras_get_stream_id(client->id, client->next_stream_id);
client->next_stream_id++;
DL_SEARCH_SCALAR(client->streams, out, id, new_id);
} while (out != NULL);
stream->id = new_id;
*stream_id_out = new_id;
stream->client = client;
/* Start the audio thread. */
rc = start_aud_thread(stream);
if (rc != 0)
return rc;
/* Start the thread associated with this stream. */
/* send a message to the server asking that the stream be started. */
rc = send_connect_message(client, stream, dev_idx);
if (rc != 0) {
stop_aud_thread(stream, 1);
return rc;
}
/* Add the stream to the linked list */
DL_APPEND(client->streams, stream);
return 0;
}
/* Removes a stream from a running client from within the running client's
* context. */
static int client_thread_rm_stream(struct cras_client *client,
cras_stream_id_t stream_id)
{
struct cras_disconnect_stream_message msg;
struct client_stream *stream =
stream_from_id(client, stream_id);
int rc;
if (stream == NULL)
return 0;
/* Tell server to remove. */
if (client->server_fd_state == CRAS_SOCKET_STATE_CONNECTED) {
cras_fill_disconnect_stream_message(&msg, stream_id);
rc = write(client->server_fd, &msg, sizeof(msg));
if (rc < 0)
syslog(LOG_ERR,
"cras_client: error removing stream from server\n");
}
/* And shut down locally. */
stop_aud_thread(stream, 1);
free_shm(stream);
DL_DELETE(client->streams, stream);
if (stream->aud_fd >= 0)
close(stream->aud_fd);
free(stream->config);
free(stream);
return 0;
}
/* Sets the volume scaling factor for a playing stream. */
static int client_thread_set_stream_volume(struct cras_client *client,
cras_stream_id_t stream_id,
float volume_scaler)
{
struct client_stream *stream;
stream = stream_from_id(client, stream_id);
if (stream == NULL || volume_scaler > 1.0 || volume_scaler < 0.0)
return -EINVAL;
stream->volume_scaler = volume_scaler;
if (stream->play_shm.area != NULL)
cras_shm_set_volume_scaler(&stream->play_shm, volume_scaler);
return 0;
}
/* Attach to the shm region containing the server state. */
static int client_attach_shm(struct cras_client *client, int shm_fd)
{
int lock_rc;
int rc;
lock_rc = server_state_wrlock(client);
if (client->server_state) {
rc = -EBUSY;
goto error;
}
client->server_state = (struct cras_server_state *)mmap(
NULL, sizeof(*client->server_state),
PROT_READ, MAP_SHARED, shm_fd, 0);
rc = -errno;
close(shm_fd);
if (client->server_state == (struct cras_server_state *)-1) {
syslog(LOG_ERR,
"cras_client: mmap failed to map shm for client: %s",
strerror(-rc));
goto error;
}
if (client->server_state->state_version != CRAS_SERVER_STATE_VERSION) {
munmap((void *)client->server_state,
sizeof(*client->server_state));
client->server_state = NULL;
rc = -EINVAL;
syslog(LOG_ERR, "cras_client: Unknown server_state version.");
} else {
rc = 0;
}
error:
server_state_unlock(client, lock_rc);
return rc;
}
static void cras_client_get_hotword_models_ready(
struct cras_client *client,
const char *hotword_models)
{
if (!client->get_hotword_models_cb)
return;
client->get_hotword_models_cb(client, hotword_models);
client->get_hotword_models_cb = NULL;
}
/* Handles messages from the cras server. */
static int handle_message_from_server(struct cras_client *client)
{
uint8_t buf[CRAS_CLIENT_MAX_MSG_SIZE];
struct cras_client_message *msg;
int rc = 0;
int nread;
int server_fds[2];
unsigned int num_fds = 2;
msg = (struct cras_client_message *)buf;
nread = cras_recv_with_fds(client->server_fd, buf, sizeof(buf),
server_fds, &num_fds);
if (nread < (int)sizeof(msg->length) || (int)msg->length != nread)
return -EIO;
switch (msg->id) {
case CRAS_CLIENT_CONNECTED: {
struct cras_client_connected *cmsg =
(struct cras_client_connected *)msg;
if (num_fds != 1)
return -EINVAL;
rc = client_attach_shm(client, server_fds[0]);
if (rc)
return rc;
client->id = cmsg->client_id;
break;
}
case CRAS_CLIENT_STREAM_CONNECTED: {
struct cras_client_stream_connected *cmsg =
(struct cras_client_stream_connected *)msg;
struct client_stream *stream =
stream_from_id(client, cmsg->stream_id);
if (stream == NULL) {
if (num_fds != 2) {
syslog(LOG_ERR, "cras_client: Error receiving "
"stream 0x%x connected message",
cmsg->stream_id);
return -EINVAL;
}
/*
* Usually, the fds should be closed in stream_connected
* callback. However, sometimes a stream is removed
* before it is connected.
*/
close(server_fds[0]);
close(server_fds[1]);
break;
}
rc = stream_connected(stream, cmsg, server_fds, num_fds);
if (rc < 0)
stream->config->err_cb(stream->client,
stream->id,
rc,
stream->config->user_data);
break;
}
case CRAS_CLIENT_AUDIO_DEBUG_INFO_READY:
if (client->debug_info_callback)
client->debug_info_callback(client);
client->debug_info_callback = NULL;
break;
case CRAS_CLIENT_GET_HOTWORD_MODELS_READY: {
struct cras_client_get_hotword_models_ready *cmsg =
(struct cras_client_get_hotword_models_ready *)msg;
cras_client_get_hotword_models_ready(client,
(const char *)cmsg->hotword_models);
break;
}
case CRAS_CLIENT_OUTPUT_VOLUME_CHANGED: {
struct cras_client_volume_changed *cmsg =
(struct cras_client_volume_changed *)msg;
if (client->observer_ops.output_volume_changed)
client->observer_ops.output_volume_changed(
client->observer_context,
cmsg->volume);
break;
}
case CRAS_CLIENT_OUTPUT_MUTE_CHANGED: {
struct cras_client_mute_changed *cmsg =
(struct cras_client_mute_changed *)msg;
if (client->observer_ops.output_mute_changed)
client->observer_ops.output_mute_changed(
client->observer_context,
cmsg->muted,
cmsg->user_muted,
cmsg->mute_locked);
break;
}
case CRAS_CLIENT_CAPTURE_GAIN_CHANGED: {
struct cras_client_volume_changed *cmsg =
(struct cras_client_volume_changed *)msg;
if (client->observer_ops.capture_gain_changed)
client->observer_ops.capture_gain_changed(
client->observer_context,
cmsg->volume);
break;
}
case CRAS_CLIENT_CAPTURE_MUTE_CHANGED: {
struct cras_client_mute_changed *cmsg =
(struct cras_client_mute_changed *)msg;
if (client->observer_ops.capture_mute_changed)
client->observer_ops.capture_mute_changed(
client->observer_context,
cmsg->muted,
cmsg->mute_locked);
break;
}
case CRAS_CLIENT_NODES_CHANGED: {
if (client->observer_ops.nodes_changed)
client->observer_ops.nodes_changed(
client->observer_context);
break;
}
case CRAS_CLIENT_ACTIVE_NODE_CHANGED: {
struct cras_client_active_node_changed *cmsg =
(struct cras_client_active_node_changed *)msg;
enum CRAS_STREAM_DIRECTION direction =
(enum CRAS_STREAM_DIRECTION)cmsg->direction;
if (client->observer_ops.active_node_changed)
client->observer_ops.active_node_changed(
client->observer_context,
direction, cmsg->node_id);
break;
}
case CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED: {
struct cras_client_node_value_changed *cmsg =
(struct cras_client_node_value_changed *)msg;
if (client->observer_ops.output_node_volume_changed)
client->observer_ops.output_node_volume_changed(
client->observer_context,
cmsg->node_id,
cmsg->value);
break;
}
case CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED: {
struct cras_client_node_value_changed *cmsg =
(struct cras_client_node_value_changed *)msg;
if (client->observer_ops.node_left_right_swapped_changed)
client->observer_ops.node_left_right_swapped_changed(
client->observer_context,
cmsg->node_id,
cmsg->value);
break;
}
case CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED: {
struct cras_client_node_value_changed *cmsg =
(struct cras_client_node_value_changed *)msg;
if (client->observer_ops.input_node_gain_changed)
client->observer_ops.input_node_gain_changed(
client->observer_context,
cmsg->node_id,
cmsg->value);
break;
}
case CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED: {
struct cras_client_num_active_streams_changed *cmsg =
(struct cras_client_num_active_streams_changed *)msg;
enum CRAS_STREAM_DIRECTION direction =
(enum CRAS_STREAM_DIRECTION)cmsg->direction;
if (client->observer_ops.num_active_streams_changed)
client->observer_ops.num_active_streams_changed(
client->observer_context,
direction, cmsg->num_active_streams);
break;
}
default:
break;
}
return 0;
}
/* Handles messages from streams to this client. */
static int handle_stream_message(struct cras_client *client,
int poll_revents)
{
struct stream_msg msg;
int rc;
if ((poll_revents & POLLIN) == 0)
return 0;
rc = read(client->stream_fds[0], &msg, sizeof(msg));
if (rc < 0)
syslog(LOG_ERR, "cras_client: Stream read failed %d\n", errno);
/* The only reason a stream sends a message is if it needs to be
* removed. An error on read would mean the same thing so regardless of
* what gets us here, just remove the stream */
client_thread_rm_stream(client, msg.stream_id);
return 0;
}
/* Handles messages from users to this client. */
static int handle_command_message(struct cras_client *client,
int poll_revents)
{
uint8_t buf[MAX_CMD_MSG_LEN];
struct command_msg *msg = (struct command_msg *)buf;
int rc, to_read;
if ((poll_revents & POLLIN) == 0)
return 0;
rc = read(client->command_fds[0], buf, sizeof(msg->len));
if (rc != sizeof(msg->len) || msg->len > MAX_CMD_MSG_LEN) {
rc = -EIO;
goto cmd_msg_complete;
}
to_read = msg->len - rc;
rc = read(client->command_fds[0], &buf[0] + rc, to_read);
if (rc != to_read) {
rc = -EIO;
goto cmd_msg_complete;
}
switch (msg->msg_id) {
case CLIENT_STOP: {
struct client_stream *s;
/* Stop all playing streams */
DL_FOREACH(client->streams, s)
client_thread_rm_stream(client, s->id);
/* And stop this client */
client->thread.state = CRAS_THREAD_STOP;
rc = 0;
break;
}
case CLIENT_ADD_STREAM: {
struct add_stream_command_message *add_msg =
(struct add_stream_command_message *)msg;
rc = client_thread_add_stream(client,
add_msg->stream,
add_msg->stream_id_out,
add_msg->dev_idx);
break;
}
case CLIENT_REMOVE_STREAM:
rc = client_thread_rm_stream(client, msg->stream_id);
break;
case CLIENT_SET_STREAM_VOLUME_SCALER: {
struct set_stream_volume_command_message *vol_msg =
(struct set_stream_volume_command_message *)msg;
rc = client_thread_set_stream_volume(client,
vol_msg->header.stream_id,
vol_msg->volume_scaler);
break;
}
case CLIENT_SERVER_CONNECT:
rc = connect_to_server_wait(client, false);
break;
case CLIENT_SERVER_CONNECT_ASYNC:
rc = server_connect(client);
break;
default:
assert(0);
break;
}
cmd_msg_complete:
/* Wake the waiting main thread with the result of the command. */
if (write(client->command_reply_fds[1], &rc, sizeof(rc)) != sizeof(rc))
return -EIO;
return rc;
}
/* This thread handles non audio sample communication with the audio server.
* The client program will call fucntions below to send messages to this thread
* to add or remove streams or change parameters.
*/
static void *client_thread(void *arg)
{
struct cras_client *client = (struct cras_client *)arg;
struct pollfd pollfds[4];
int (*cbs[4])(struct cras_client *client, int poll_revents);
unsigned int num_pollfds, i;
int rc;
if (arg == NULL)
return (void *)-EINVAL;
while (thread_is_running(&client->thread)) {
num_pollfds = 0;
rc = cras_file_wait_get_fd(client->sock_file_wait);
if (rc >= 0) {
cbs[num_pollfds] = sock_file_wait_dispatch;
pollfds[num_pollfds].fd = rc;
pollfds[num_pollfds].events = POLLIN;
pollfds[num_pollfds].revents = 0;
num_pollfds++;
}
else
syslog(LOG_ERR, "file wait fd: %d", rc);
if (client->server_fd >= 0) {
cbs[num_pollfds] = server_fd_dispatch;
server_fill_pollfd(client, &(pollfds[num_pollfds]));
num_pollfds++;
}
if (client->command_fds[0] >= 0) {
cbs[num_pollfds] = handle_command_message;
pollfds[num_pollfds].fd = client->command_fds[0];
pollfds[num_pollfds].events = POLLIN;
pollfds[num_pollfds].revents = 0;
num_pollfds++;
}
if (client->stream_fds[0] >= 0) {
cbs[num_pollfds] = handle_stream_message;
pollfds[num_pollfds].fd = client->stream_fds[0];
pollfds[num_pollfds].events = POLLIN;
pollfds[num_pollfds].revents = 0;
num_pollfds++;
}
rc = poll(pollfds, num_pollfds, -1);
if (rc <= 0)
continue;
for (i = 0; i < num_pollfds; i++) {
/* Only do one at a time, since some messages may
* result in change to other fds. */
if (pollfds[i].revents) {
cbs[i](client, pollfds[i].revents);
break;
}
}
}
/* close the command reply pipe. */
close(client->command_reply_fds[1]);
client->command_reply_fds[1] = -1;
return NULL;
}
/* Sends a message to the client thread to complete an action requested by the
* user. Then waits for the action to complete and returns the result. */
static int send_command_message(struct cras_client *client,
struct command_msg *msg)
{
int rc, cmd_res;
if (client == NULL || !thread_is_running(&client->thread))
return -EINVAL;
rc = write(client->command_fds[1], msg, msg->len);
if (rc != (int)msg->len)
return -EPIPE;
/* Wait for command to complete. */
rc = read(client->command_reply_fds[0], &cmd_res, sizeof(cmd_res));
if (rc != sizeof(cmd_res))
return -EPIPE;
return cmd_res;
}
/* Send a simple message to the client thread that holds no data. */
static int send_simple_cmd_msg(struct cras_client *client,
cras_stream_id_t stream_id,
unsigned msg_id)
{
struct command_msg msg;
msg.len = sizeof(msg);
msg.stream_id = stream_id;
msg.msg_id = msg_id;
return send_command_message(client, &msg);
}
/* Sends the set volume message to the client thread. */
static int send_stream_volume_command_msg(struct cras_client *client,
cras_stream_id_t stream_id,
float volume_scaler)
{
struct set_stream_volume_command_message msg;
msg.header.len = sizeof(msg);
msg.header.stream_id = stream_id;
msg.header.msg_id = CLIENT_SET_STREAM_VOLUME_SCALER;
msg.volume_scaler = volume_scaler;
return send_command_message(client, &msg.header);
}
/* Sends a message back to the client and returns the error code. */
static int write_message_to_server(struct cras_client *client,
const struct cras_server_message *msg)
{
ssize_t write_rc = -EPIPE;
if (client->server_fd_state == CRAS_SOCKET_STATE_CONNECTED ||
client->server_fd_state == CRAS_SOCKET_STATE_FIRST_MESSAGE) {
write_rc = write(client->server_fd, msg, msg->length);
if (write_rc < 0)
write_rc = -errno;
}
if (write_rc != (ssize_t)msg->length &&
client->server_fd_state != CRAS_SOCKET_STATE_FIRST_MESSAGE)
return -EPIPE;
if (write_rc < 0)
return write_rc;
else if (write_rc != (ssize_t)msg->length)
return -EIO;
else
return 0;
}
/*
* Exported Client Interface
*/
int cras_client_create(struct cras_client **client)
{
const char *sock_dir;
size_t sock_file_size;
int rc;
struct client_int *client_int;
pthread_condattr_t cond_attr;
/* Ignore SIGPIPE while using this API. */
signal(SIGPIPE, SIG_IGN);
sock_dir = cras_config_get_system_socket_file_dir();
if (!sock_dir)
return -ENOMEM;
client_int = (struct client_int *)calloc(1, sizeof(*client_int));
if (!client_int)
return -ENOMEM;
*client = &client_int->client;
(*client)->server_fd = -1;
(*client)->id = -1;
rc = pthread_rwlock_init(&client_int->server_state_rwlock, NULL);
if (rc != 0) {
syslog(LOG_ERR, "cras_client: Could not init state rwlock.");
rc = -rc;
goto free_client;
}
rc = pthread_mutex_init(&(*client)->stream_start_lock, NULL);
if (rc != 0) {
syslog(LOG_ERR, "cras_client: Could not init start lock.");
rc = -rc;
goto free_rwlock;
}
pthread_condattr_init(&cond_attr);
pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
rc = pthread_cond_init(&(*client)->stream_start_cond, &cond_attr);
pthread_condattr_destroy(&cond_attr);
if (rc != 0) {
syslog(LOG_ERR, "cras_client: Could not init start cond.");
rc = -rc;
goto free_lock;
}
(*client)->server_event_fd = eventfd(0, EFD_CLOEXEC|EFD_NONBLOCK);
if ((*client)->server_event_fd < 0) {
syslog(LOG_ERR, "cras_client: Could not setup server eventfd.");
rc = -errno;
goto free_cond;
}
sock_file_size = strlen(sock_dir) + strlen(CRAS_SOCKET_FILE) + 2;
(*client)->sock_file = (const char *)malloc(sock_file_size);
if (!(*client)->sock_file) {
rc = -ENOMEM;
goto free_error;
}
snprintf((char *)(*client)->sock_file, sock_file_size, "%s/%s", sock_dir,
CRAS_SOCKET_FILE);
rc = cras_file_wait_create((*client)->sock_file,
CRAS_FILE_WAIT_FLAG_NONE,
sock_file_wait_callback, *client,
&(*client)->sock_file_wait);
if (rc != 0 && rc != -ENOENT) {
syslog(LOG_ERR, "cras_client: Could not setup watch for '%s'.",
(*client)->sock_file);
goto free_error;
}
(*client)->sock_file_exists = (rc == 0);
/* Pipes used by the main thread and the client thread to send commands
* and replies. */
rc = pipe((*client)->command_fds);
if (rc < 0)
goto free_error;
/* Pipe used to communicate between the client thread and the audio
* thread. */
rc = pipe((*client)->stream_fds);
if (rc < 0) {
close((*client)->command_fds[0]);
close((*client)->command_fds[1]);
goto free_error;
}
(*client)->command_reply_fds[0] = -1;
(*client)->command_reply_fds[1] = -1;
return 0;
free_error:
if ((*client)->server_event_fd >= 0)
close((*client)->server_event_fd);
cras_file_wait_destroy((*client)->sock_file_wait);
free((void *)(*client)->sock_file);
free_cond:
pthread_cond_destroy(&(*client)->stream_start_cond);
free_lock:
pthread_mutex_destroy(&(*client)->stream_start_lock);
free_rwlock:
pthread_rwlock_destroy(&client_int->server_state_rwlock);
free_client:
*client = NULL;
free(client_int);
return rc;
}
void cras_client_destroy(struct cras_client *client)
{
struct client_int *client_int;
if (client == NULL)
return;
client_int = to_client_int(client);
client->server_connection_cb = NULL;
client->server_err_cb = NULL;
cras_client_stop(client);
server_disconnect(client);
close(client->server_event_fd);
close(client->command_fds[0]);
close(client->command_fds[1]);
close(client->stream_fds[0]);
close(client->stream_fds[1]);
cras_file_wait_destroy(client->sock_file_wait);
pthread_rwlock_destroy(&client_int->server_state_rwlock);
free((void *)client->sock_file);
free(client_int);
}
int cras_client_connect(struct cras_client *client)
{
return connect_to_server(client, NULL, true);
}
int cras_client_connect_timeout(struct cras_client *client,
unsigned int timeout_ms)
{
return connect_to_server_wait_retry(client, timeout_ms, true);
}
int cras_client_connected_wait(struct cras_client *client)
{
return send_simple_cmd_msg(client, 0, CLIENT_SERVER_CONNECT);
}
int cras_client_connect_async(struct cras_client *client)
{
return send_simple_cmd_msg(client, 0, CLIENT_SERVER_CONNECT_ASYNC);
}
struct cras_stream_params *cras_client_stream_params_create(
enum CRAS_STREAM_DIRECTION direction,
size_t buffer_frames,
size_t cb_threshold,
size_t unused,
enum CRAS_STREAM_TYPE stream_type,
uint32_t flags,
void *user_data,
cras_playback_cb_t aud_cb,
cras_error_cb_t err_cb,
struct cras_audio_format *format)
{
struct cras_stream_params *params;
params = (struct cras_stream_params *)malloc(sizeof(*params));
if (params == NULL)
return NULL;
params->direction = direction;
params->buffer_frames = buffer_frames;
params->cb_threshold = cb_threshold;
params->effects = 0;
params->stream_type = stream_type;
params->flags = flags;
params->user_data = user_data;
params->aud_cb = aud_cb;
params->unified_cb = 0;
params->err_cb = err_cb;
memcpy(&(params->format), format, sizeof(*format));
return params;
}
void cras_client_stream_params_enable_aec(struct cras_stream_params *params)
{
params->effects |= APM_ECHO_CANCELLATION;
}
void cras_client_stream_params_disable_aec(struct cras_stream_params *params)
{
params->effects &= ~APM_ECHO_CANCELLATION;
}
void cras_client_stream_params_enable_ns(struct cras_stream_params *params)
{
params->effects |= APM_NOISE_SUPRESSION;
}
void cras_client_stream_params_disable_ns(struct cras_stream_params *params)
{
params->effects &= ~APM_NOISE_SUPRESSION;
}
void cras_client_stream_params_enable_agc(struct cras_stream_params *params)
{
params->effects |= APM_GAIN_CONTROL;
}
void cras_client_stream_params_disable_agc(struct cras_stream_params *params)
{
params->effects &= ~APM_GAIN_CONTROL;
}
void cras_client_stream_params_enable_vad(struct cras_stream_params *params)
{
params->effects |= APM_VOICE_DETECTION;
}
void cras_client_stream_params_disable_vad(struct cras_stream_params *params)
{
params->effects &= ~APM_VOICE_DETECTION;
}
struct cras_stream_params *cras_client_unified_params_create(
enum CRAS_STREAM_DIRECTION direction,
unsigned int block_size,
enum CRAS_STREAM_TYPE stream_type,
uint32_t flags,
void *user_data,
cras_unified_cb_t unified_cb,
cras_error_cb_t err_cb,
struct cras_audio_format *format)
{
struct cras_stream_params *params;
params = (struct cras_stream_params *)malloc(sizeof(*params));
if (params == NULL)
return NULL;
params->direction = direction;
params->buffer_frames = block_size * 2;
params->cb_threshold = block_size;
params->stream_type = stream_type;
params->flags = flags;
params->effects = 0;
params->user_data = user_data;
params->aud_cb = 0;
params->unified_cb = unified_cb;
params->err_cb = err_cb;
memcpy(&(params->format), format, sizeof(*format));
return params;
}
void cras_client_stream_params_destroy(struct cras_stream_params *params)
{
free(params);
}
static inline int cras_client_send_add_stream_command_message(
struct cras_client *client,
uint32_t dev_idx,
cras_stream_id_t *stream_id_out,
struct cras_stream_params *config)
{
struct add_stream_command_message cmd_msg;
struct client_stream *stream;
int rc = 0;
if (client == NULL || config == NULL || stream_id_out == NULL)
return -EINVAL;
if (config->aud_cb == NULL && config->unified_cb == NULL)
return -EINVAL;
if (config->err_cb == NULL)
return -EINVAL;
stream = (struct client_stream *)calloc(1, sizeof(*stream));
if (stream == NULL) {
rc = -ENOMEM;
goto add_failed;
}
stream->config = (struct cras_stream_params *)
malloc(sizeof(*(stream->config)));
if (stream->config == NULL) {
rc = -ENOMEM;
goto add_failed;
}
memcpy(stream->config, config, sizeof(*config));
stream->aud_fd = -1;
stream->wake_fds[0] = -1;
stream->wake_fds[1] = -1;
stream->direction = config->direction;
stream->volume_scaler = 1.0;
stream->flags = config->flags;
cmd_msg.header.len = sizeof(cmd_msg);
cmd_msg.header.msg_id = CLIENT_ADD_STREAM;
cmd_msg.header.stream_id = stream->id;
cmd_msg.stream = stream;
cmd_msg.stream_id_out = stream_id_out;
cmd_msg.dev_idx = dev_idx;
rc = send_command_message(client, &cmd_msg.header);
if (rc < 0) {
syslog(LOG_ERR,
"cras_client: adding stream failed in thread %d", rc);
goto add_failed;
}
return 0;
add_failed:
if (stream) {
if (stream->config)
free(stream->config);
free(stream);
}
return rc;
}
int cras_client_add_stream(struct cras_client *client,
cras_stream_id_t *stream_id_out,
struct cras_stream_params *config)
{
return cras_client_send_add_stream_command_message(
client,
NO_DEVICE,
stream_id_out,
config);
}
int cras_client_add_pinned_stream(struct cras_client *client,
uint32_t dev_idx,
cras_stream_id_t *stream_id_out,
struct cras_stream_params *config)
{
return cras_client_send_add_stream_command_message(
client,
dev_idx,
stream_id_out,
config);
}
int cras_client_rm_stream(struct cras_client *client,
cras_stream_id_t stream_id)
{
if (client == NULL)
return -EINVAL;
return send_simple_cmd_msg(client, stream_id, CLIENT_REMOVE_STREAM);
}
int cras_client_set_stream_volume(struct cras_client *client,
cras_stream_id_t stream_id,
float volume_scaler)
{
if (client == NULL)
return -EINVAL;
return send_stream_volume_command_msg(client, stream_id, volume_scaler);
}
int cras_client_set_system_volume(struct cras_client *client, size_t volume)
{
struct cras_set_system_volume msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_system_volume(&msg, volume);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_system_capture_gain(struct cras_client *client, long gain)
{
struct cras_set_system_capture_gain msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_system_capture_gain(&msg, gain);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_system_mute(struct cras_client *client, int mute)
{
struct cras_set_system_mute msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_system_mute(&msg, mute);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_user_mute(struct cras_client *client, int mute)
{
struct cras_set_system_mute msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_user_mute(&msg, mute);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_system_mute_locked(struct cras_client *client, int locked)
{
struct cras_set_system_mute msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_system_mute_locked(&msg, locked);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_system_capture_mute(struct cras_client *client, int mute)
{
struct cras_set_system_mute msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_system_capture_mute(&msg, mute);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_system_capture_mute_locked(struct cras_client *client,
int locked)
{
struct cras_set_system_mute msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_system_capture_mute_locked(&msg, locked);
return write_message_to_server(client, &msg.header);
}
size_t cras_client_get_system_volume(const struct cras_client *client)
{
size_t volume;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
volume = client->server_state->volume;
server_state_unlock(client, lock_rc);
return volume;
}
long cras_client_get_system_capture_gain(const struct cras_client *client)
{
long gain;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
gain = client->server_state->capture_gain;
server_state_unlock(client, lock_rc);
return gain;
}
int cras_client_get_system_muted(const struct cras_client *client)
{
int muted;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
muted = client->server_state->mute;
server_state_unlock(client, lock_rc);
return muted;
}
int cras_client_get_user_muted(const struct cras_client *client)
{
int muted;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
muted = client->server_state->user_mute;
server_state_unlock(client, lock_rc);
return muted;
}
int cras_client_get_system_capture_muted(const struct cras_client *client)
{
int muted;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
muted = client->server_state->capture_mute;
server_state_unlock(client, lock_rc);
return muted;
}
long cras_client_get_system_min_volume(const struct cras_client *client)
{
long min_volume;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
min_volume = client->server_state->min_volume_dBFS;
server_state_unlock(client, lock_rc);
return min_volume;
}
long cras_client_get_system_max_volume(const struct cras_client *client)
{
long max_volume;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
max_volume = client->server_state->max_volume_dBFS;
server_state_unlock(client, lock_rc);
return max_volume;
}
long cras_client_get_system_min_capture_gain(const struct cras_client *client)
{
long min_gain;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
min_gain = client->server_state->min_capture_gain;
server_state_unlock(client, lock_rc);
return min_gain;
}
long cras_client_get_system_max_capture_gain(const struct cras_client *client)
{
long max_gain;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
max_gain = client->server_state->max_capture_gain;
server_state_unlock(client, lock_rc);
return max_gain;
}
const struct audio_debug_info *cras_client_get_audio_debug_info(
const struct cras_client *client)
{
const struct audio_debug_info *debug_info;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
debug_info = &client->server_state->audio_debug_info;
server_state_unlock(client, lock_rc);
return debug_info;
}
const struct cras_audio_thread_snapshot_buffer*
cras_client_get_audio_thread_snapshot_buffer(
const struct cras_client *client)
{
const struct cras_audio_thread_snapshot_buffer *snapshot_buffer;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
snapshot_buffer = &client->server_state->snapshot_buffer;
server_state_unlock(client, lock_rc);
return snapshot_buffer;
}
unsigned cras_client_get_num_active_streams(const struct cras_client *client,
struct timespec *ts)
{
unsigned num_streams, version, i;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
read_active_streams_again:
version = begin_server_state_read(client->server_state);
num_streams = 0;
for (i = 0; i < CRAS_NUM_DIRECTIONS; i++)
num_streams += client->server_state->num_active_streams[i];
if (ts) {
if (num_streams)
clock_gettime(CLOCK_MONOTONIC_RAW, ts);
else
cras_timespec_to_timespec(ts,
&client->server_state->last_active_stream_time);
}
if (end_server_state_read(client->server_state, version))
goto read_active_streams_again;
server_state_unlock(client, lock_rc);
return num_streams;
}
int cras_client_run_thread(struct cras_client *client)
{
int rc;
if (client == NULL)
return -EINVAL;
if (thread_is_running(&client->thread))
return 0;
assert(client->command_reply_fds[0] == -1 &&
client->command_reply_fds[1] == -1);
if (pipe(client->command_reply_fds) < 0)
return -EIO;
client->thread.state = CRAS_THREAD_RUNNING;
rc = pthread_create(&client->thread.tid, NULL, client_thread, client);
if (rc) {
client->thread.state = CRAS_THREAD_STOP;
return -rc;
}
return 0;
}
int cras_client_stop(struct cras_client *client)
{
if (client == NULL)
return -EINVAL;
if (!thread_is_running(&client->thread))
return 0;
send_simple_cmd_msg(client, 0, CLIENT_STOP);
pthread_join(client->thread.tid, NULL);
/* The other end of the reply pipe is closed by the client thread, just
* clost the read end here. */
close(client->command_reply_fds[0]);
client->command_reply_fds[0] = -1;
return 0;
}
void cras_client_set_server_error_cb(struct cras_client *client,
cras_server_error_cb_t err_cb,
void *user_arg)
{
client->server_err_cb = err_cb;
client->server_connection_user_arg = user_arg;
}
void cras_client_set_connection_status_cb(
struct cras_client *client,
cras_connection_status_cb_t connection_cb,
void *user_arg)
{
client->server_connection_cb = connection_cb;
client->server_connection_user_arg = user_arg;
}
void cras_client_set_thread_priority_cb(struct cras_client *client,
cras_thread_priority_cb_t cb)
{
client->thread_priority_cb = cb;
}
int cras_client_get_output_devices(const struct cras_client *client,
struct cras_iodev_info *devs,
struct cras_ionode_info *nodes,
size_t *num_devs, size_t *num_nodes)
{
const struct cras_server_state *state;
unsigned avail_devs, avail_nodes, version;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return -EINVAL;
state = client->server_state;
read_outputs_again:
version = begin_server_state_read(state);
avail_devs = MIN(*num_devs, state->num_output_devs);
memcpy(devs, state->output_devs, avail_devs * sizeof(*devs));
avail_nodes = MIN(*num_nodes, state->num_output_nodes);
memcpy(nodes, state->output_nodes, avail_nodes * sizeof(*nodes));
if (end_server_state_read(state, version))
goto read_outputs_again;
server_state_unlock(client, lock_rc);
*num_devs = avail_devs;
*num_nodes = avail_nodes;
return 0;
}
int cras_client_get_input_devices(const struct cras_client *client,
struct cras_iodev_info *devs,
struct cras_ionode_info *nodes,
size_t *num_devs, size_t *num_nodes)
{
const struct cras_server_state *state;
unsigned avail_devs, avail_nodes, version;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (!client)
return -EINVAL;
state = client->server_state;
read_inputs_again:
version = begin_server_state_read(state);
avail_devs = MIN(*num_devs, state->num_input_devs);
memcpy(devs, state->input_devs, avail_devs * sizeof(*devs));
avail_nodes = MIN(*num_nodes, state->num_input_nodes);
memcpy(nodes, state->input_nodes, avail_nodes * sizeof(*nodes));
if (end_server_state_read(state, version))
goto read_inputs_again;
server_state_unlock(client, lock_rc);
*num_devs = avail_devs;
*num_nodes = avail_nodes;
return 0;
}
int cras_client_get_attached_clients(const struct cras_client *client,
struct cras_attached_client_info *clients,
size_t max_clients)
{
const struct cras_server_state *state;
unsigned num, version;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return -EINVAL;
state = client->server_state;
read_clients_again:
version = begin_server_state_read(state);
num = MIN(max_clients, state->num_attached_clients);
memcpy(clients, state->client_info, num * sizeof(*clients));
if (end_server_state_read(state, version))
goto read_clients_again;
server_state_unlock(client, lock_rc);
return num;
}
/* Find an output ionode on an iodev with the matching name.
*
* Args:
* dev_name - The prefix of the iodev name.
* node_name - The prefix of the ionode name.
* dev_info - The information about the iodev will be returned here.
* node_info - The information about the ionode will be returned here.
* Returns:
* 0 if successful, -1 if the node cannot be found.
*/
static int cras_client_find_output_node(const struct cras_client *client,
const char *dev_name,
const char *node_name,
struct cras_iodev_info *dev_info,
struct cras_ionode_info *node_info)
{
size_t ndevs, nnodes;
struct cras_iodev_info *devs = NULL;
struct cras_ionode_info *nodes = NULL;
int rc = -1;
unsigned i, j;
if (!client || !dev_name || !node_name)
goto quit;
devs = (struct cras_iodev_info *)
malloc(CRAS_MAX_IODEVS * sizeof(*devs));
if (!devs)
goto quit;
nodes = (struct cras_ionode_info *)
malloc(CRAS_MAX_IONODES * sizeof(*nodes));
if (!nodes)
goto quit;
ndevs = CRAS_MAX_IODEVS;
nnodes = CRAS_MAX_IONODES;
rc = cras_client_get_output_devices(client, devs, nodes, &ndevs,
&nnodes);
if (rc < 0)
goto quit;
for (i = 0; i < ndevs; i++)
if (!strncmp(dev_name, devs[i].name, strlen(dev_name)))
goto found_dev;
rc = -1;
goto quit;
found_dev:
for (j = 0; j < nnodes; j++)
if (nodes[j].iodev_idx == devs[i].idx &&
!strncmp(node_name, nodes[j].name, strlen(node_name)))
goto found_node;
rc = -1;
goto quit;
found_node:
*dev_info = devs[i];
*node_info = nodes[j];
rc = 0;
quit:
free(devs);
free(nodes);
return rc;
}
int cras_client_get_node_by_id(const struct cras_client *client,
int input,
const cras_node_id_t node_id,
struct cras_ionode_info* node_info)
{
size_t ndevs, nnodes;
struct cras_iodev_info *devs = NULL;
struct cras_ionode_info *nodes = NULL;
int rc = -EINVAL;
unsigned i;
if (!client || !node_info) {
rc = -EINVAL;
goto quit;
}
devs = (struct cras_iodev_info *)
malloc(CRAS_MAX_IODEVS * sizeof(*devs));
if (!devs) {
rc = -ENOMEM;
goto quit;
}
nodes = (struct cras_ionode_info *)
malloc(CRAS_MAX_IONODES * sizeof(*nodes));
if (!nodes) {
rc = -ENOMEM;
goto quit;
}
ndevs = CRAS_MAX_IODEVS;
nnodes = CRAS_MAX_IONODES;
if (input)
rc = cras_client_get_input_devices(client, devs, nodes,
&ndevs, &nnodes);
else
rc = cras_client_get_output_devices(client, devs, nodes,
&ndevs, &nnodes);
if (rc < 0)
goto quit;
rc = -ENOENT;
for (i = 0; i < nnodes; i++) {
if (node_id == cras_make_node_id(nodes[i].iodev_idx,
nodes[i].ionode_idx)) {
memcpy(node_info, &nodes[i], sizeof(*node_info));
rc = 0;
break;
}
}
quit:
free(devs);
free(nodes);
return rc;
}
int cras_client_output_dev_plugged(const struct cras_client *client,
const char *name)
{
struct cras_iodev_info dev_info;
struct cras_ionode_info node_info = { 0 };
if (cras_client_find_output_node(client, name, "Front Headphone Jack",
&dev_info, &node_info) < 0)
return 0;
return node_info.plugged;
}
int cras_client_set_node_attr(struct cras_client *client,
cras_node_id_t node_id,
enum ionode_attr attr, int value)
{
struct cras_set_node_attr msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_node_attr(&msg, node_id, attr, value);
return write_message_to_server(client, &msg.header);
}
int cras_client_select_node(struct cras_client *client,
enum CRAS_STREAM_DIRECTION direction,
cras_node_id_t node_id)
{
struct cras_select_node msg;
if (client == NULL)
return -EINVAL;
cras_fill_select_node(&msg, direction, node_id);
return write_message_to_server(client, &msg.header);
}
int cras_client_add_active_node(struct cras_client *client,
enum CRAS_STREAM_DIRECTION direction,
cras_node_id_t node_id)
{
struct cras_add_active_node msg;
if (client == NULL)
return -EINVAL;
cras_fill_add_active_node(&msg, direction, node_id);
return write_message_to_server(client, &msg.header);
}
int cras_client_rm_active_node(struct cras_client *client,
enum CRAS_STREAM_DIRECTION direction,
cras_node_id_t node_id)
{
struct cras_rm_active_node msg;
if (client == NULL)
return -EINVAL;
cras_fill_rm_active_node(&msg, direction, node_id);
return write_message_to_server(client, &msg.header);
}
int cras_client_format_bytes_per_frame(struct cras_audio_format *fmt)
{
if (fmt == NULL)
return -EINVAL;
return cras_get_format_bytes(fmt);
}
int cras_client_calc_playback_latency(const struct timespec *sample_time,
struct timespec *delay)
{
struct timespec now;
if (delay == NULL)
return -EINVAL;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
/* for output return time until sample is played (t - now) */
subtract_timespecs(sample_time, &now, delay);
return 0;
}
int cras_client_calc_capture_latency(const struct timespec *sample_time,
struct timespec *delay)
{
struct timespec now;
if (delay == NULL)
return -EINVAL;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
/* For input want time since sample read (now - t) */
subtract_timespecs(&now, sample_time, delay);
return 0;
}
int cras_client_reload_dsp(struct cras_client *client)
{
struct cras_reload_dsp msg;
if (client == NULL)
return -EINVAL;
cras_fill_reload_dsp(&msg);
return write_message_to_server(client, &msg.header);
}
int cras_client_dump_dsp_info(struct cras_client *client)
{
struct cras_dump_dsp_info msg;
if (client == NULL)
return -EINVAL;
cras_fill_dump_dsp_info(&msg);
return write_message_to_server(client, &msg.header);
}
int cras_client_update_audio_debug_info(
struct cras_client *client,
void (*debug_info_cb)(struct cras_client *))
{
struct cras_dump_audio_thread msg;
if (client == NULL)
return -EINVAL;
if (client->debug_info_callback != NULL)
return -EINVAL;
client->debug_info_callback = debug_info_cb;
cras_fill_dump_audio_thread(&msg);
return write_message_to_server(client, &msg.header);
}
int cras_client_update_audio_thread_snapshots(
struct cras_client *client,
void (*debug_info_cb)(struct cras_client *))
{
struct cras_dump_snapshots msg;
if (client == NULL)
return -EINVAL;
if (client->debug_info_callback != NULL)
return -EINVAL;
client->debug_info_callback = debug_info_cb;
cras_fill_dump_snapshots(&msg);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_node_volume(struct cras_client *client,
cras_node_id_t node_id,
uint8_t volume)
{
struct cras_set_node_attr msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_node_attr(&msg, node_id, IONODE_ATTR_VOLUME, volume);
return write_message_to_server(client, &msg.header);
}
int cras_client_swap_node_left_right(struct cras_client *client,
cras_node_id_t node_id, int enable)
{
struct cras_set_node_attr msg;
if (client == NULL)
return -EINVAL;
cras_fill_set_node_attr(&msg, node_id, IONODE_ATTR_SWAP_LEFT_RIGHT,
enable);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_node_capture_gain(struct cras_client *client,
cras_node_id_t node_id,
long gain)
{
struct cras_set_node_attr msg;
if (client == NULL)
return -EINVAL;
if (gain > INT_MAX || gain < INT_MIN)
return -EINVAL;
cras_fill_set_node_attr(&msg, node_id, IONODE_ATTR_CAPTURE_GAIN, gain);
return write_message_to_server(client, &msg.header);
}
int cras_client_add_test_iodev(struct cras_client *client,
enum TEST_IODEV_TYPE type)
{
struct cras_add_test_dev msg;
cras_fill_add_test_dev(&msg, type);
return write_message_to_server(client, &msg.header);
}
int cras_client_test_iodev_command(struct cras_client *client,
unsigned int iodev_idx,
enum CRAS_TEST_IODEV_CMD command,
unsigned int data_len,
const uint8_t *data)
{
struct cras_test_dev_command *msg;
int rc;
msg = (struct cras_test_dev_command *)malloc(sizeof(*msg) + data_len);
cras_fill_test_dev_command(msg, iodev_idx, command, data_len, data);
rc = write_message_to_server(client, &msg->header);
free(msg);
return rc;
}
int cras_client_config_global_remix(struct cras_client *client,
unsigned num_channels,
float *coefficient)
{
struct cras_config_global_remix *msg;
int rc;
msg = (struct cras_config_global_remix *)malloc(sizeof(*msg) +
num_channels * num_channels * sizeof(*coefficient));
cras_fill_config_global_remix_command(msg, num_channels,
coefficient,
num_channels * num_channels);
rc = write_message_to_server(client, &msg->header);
free(msg);
return rc;
}
int cras_client_get_first_node_type_idx(const struct cras_client *client,
enum CRAS_NODE_TYPE type,
enum CRAS_STREAM_DIRECTION direction,
cras_node_id_t *node_id)
{
const struct cras_server_state *state;
unsigned int version;
unsigned int i;
const struct cras_ionode_info *node_list;
unsigned int num_nodes;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return -EINVAL;
state = client->server_state;
read_nodes_again:
version = begin_server_state_read(state);
if (direction == CRAS_STREAM_OUTPUT) {
node_list = state->output_nodes;
num_nodes = state->num_output_nodes;
} else {
node_list = state->input_nodes;
num_nodes = state->num_input_nodes;
}
for (i = 0; i < num_nodes; i++) {
if ((enum CRAS_NODE_TYPE)node_list[i].type_enum == type) {
*node_id = cras_make_node_id(node_list[i].iodev_idx,
node_list[i].ionode_idx);
server_state_unlock(client, lock_rc);
return 0;
}
}
if (end_server_state_read(state, version))
goto read_nodes_again;
server_state_unlock(client, lock_rc);
return -ENODEV;
}
int cras_client_get_first_dev_type_idx(const struct cras_client *client,
enum CRAS_NODE_TYPE type,
enum CRAS_STREAM_DIRECTION direction)
{
cras_node_id_t node_id;
int rc;
rc = cras_client_get_first_node_type_idx(client, type, direction,
&node_id);
if (rc)
return rc;
return dev_index_of(node_id);
}
int cras_client_set_suspend(struct cras_client *client, int suspend)
{
struct cras_server_message msg;
cras_fill_suspend_message(&msg, suspend);
return write_message_to_server(client, &msg);
}
int cras_client_get_hotword_models(struct cras_client *client,
cras_node_id_t node_id,
get_hotword_models_cb_t cb)
{
struct cras_get_hotword_models msg;
if (!client)
return -EINVAL;
client->get_hotword_models_cb = cb;
cras_fill_get_hotword_models_message(&msg, node_id);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_hotword_model(struct cras_client *client,
cras_node_id_t node_id,
const char *model_name)
{
struct cras_set_hotword_model msg;
cras_fill_set_hotword_model_message(&msg, node_id, model_name);
return write_message_to_server(client, &msg.header);
}
int cras_client_set_aec_dump(struct cras_client *client,
cras_stream_id_t stream_id,
int start,
int fd)
{
struct cras_set_aec_dump msg;
cras_fill_set_aec_dump_message(&msg, stream_id, start);
if (fd != -1)
return cras_send_with_fds(client->server_fd, &msg,
sizeof(msg), &fd, 1);
else
return write_message_to_server(client, &msg.header);
}
int cras_client_reload_aec_config(struct cras_client *client)
{
struct cras_reload_aec_config msg;
cras_fill_reload_aec_config(&msg);
return write_message_to_server(client, &msg.header);
}
int cras_client_get_aec_supported(struct cras_client *client)
{
int aec_supported;
int lock_rc;
lock_rc = server_state_rdlock(client);
if (lock_rc)
return 0;
aec_supported = client->server_state->aec_supported;
server_state_unlock(client, lock_rc);
return aec_supported;
}
void cras_client_set_state_change_callback_context(
struct cras_client *client, void *context)
{
if (!client)
return;
client->observer_context = context;
}
static int cras_send_register_notification(struct cras_client *client,
enum CRAS_CLIENT_MESSAGE_ID msg_id,
int do_register)
{
struct cras_register_notification msg;
int rc;
/* This library automatically re-registers notifications when
* reconnecting, so we can ignore message send failure due to no
* connection. */
cras_fill_register_notification_message(&msg, msg_id, do_register);
rc = write_message_to_server(client, &msg.header);
if (rc == -EPIPE)
rc = 0;
return rc;
}
int cras_client_set_output_volume_changed_callback(
struct cras_client *client,
cras_client_output_volume_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.output_volume_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_OUTPUT_VOLUME_CHANGED, cb != NULL);
}
int cras_client_set_output_mute_changed_callback(
struct cras_client *client,
cras_client_output_mute_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.output_mute_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_OUTPUT_MUTE_CHANGED, cb != NULL);
}
int cras_client_set_capture_gain_changed_callback(
struct cras_client *client,
cras_client_capture_gain_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.capture_gain_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_CAPTURE_GAIN_CHANGED, cb != NULL);
}
int cras_client_set_capture_mute_changed_callback(
struct cras_client *client,
cras_client_capture_mute_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.capture_mute_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_CAPTURE_MUTE_CHANGED, cb != NULL);
}
int cras_client_set_nodes_changed_callback(
struct cras_client *client,
cras_client_nodes_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.nodes_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_NODES_CHANGED, cb != NULL);
}
int cras_client_set_active_node_changed_callback(
struct cras_client *client,
cras_client_active_node_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.active_node_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_ACTIVE_NODE_CHANGED, cb != NULL);
}
int cras_client_set_output_node_volume_changed_callback(
struct cras_client *client,
cras_client_output_node_volume_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.output_node_volume_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED, cb != NULL);
}
int cras_client_set_node_left_right_swapped_changed_callback(
struct cras_client *client,
cras_client_node_left_right_swapped_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.node_left_right_swapped_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED, cb != NULL);
}
int cras_client_set_input_node_gain_changed_callback(
struct cras_client *client,
cras_client_input_node_gain_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.input_node_gain_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED, cb != NULL);
}
int cras_client_set_num_active_streams_changed_callback(
struct cras_client *client,
cras_client_num_active_streams_changed_callback cb)
{
if (!client)
return -EINVAL;
client->observer_ops.num_active_streams_changed = cb;
return cras_send_register_notification(
client, CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED, cb != NULL);
}
static int reregister_notifications(struct cras_client *client)
{
int rc;
if (client->observer_ops.output_volume_changed) {
rc = cras_client_set_output_volume_changed_callback(
client,
client->observer_ops.output_volume_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.output_mute_changed) {
rc = cras_client_set_output_mute_changed_callback(
client,
client->observer_ops.output_mute_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.capture_gain_changed) {
rc = cras_client_set_capture_gain_changed_callback(
client,
client->observer_ops.capture_gain_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.capture_mute_changed) {
rc = cras_client_set_capture_mute_changed_callback(
client,
client->observer_ops.capture_mute_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.nodes_changed) {
rc = cras_client_set_nodes_changed_callback(
client, client->observer_ops.nodes_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.active_node_changed) {
rc = cras_client_set_active_node_changed_callback(
client,
client->observer_ops.active_node_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.output_node_volume_changed) {
rc = cras_client_set_output_node_volume_changed_callback(
client,
client->observer_ops.output_node_volume_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.node_left_right_swapped_changed) {
rc = cras_client_set_node_left_right_swapped_changed_callback(
client,
client->observer_ops.node_left_right_swapped_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.input_node_gain_changed) {
rc = cras_client_set_input_node_gain_changed_callback(
client,
client->observer_ops.input_node_gain_changed);
if (rc != 0)
return rc;
}
if (client->observer_ops.num_active_streams_changed) {
rc = cras_client_set_num_active_streams_changed_callback(
client,
client->observer_ops.num_active_streams_changed);
if (rc != 0)
return rc;
}
return 0;
}
static int hotword_read_cb(struct cras_client *client,
cras_stream_id_t stream_id,
uint8_t *captured_samples,
uint8_t *playback_samples,
unsigned int frames,
const struct timespec *captured_time,
const struct timespec *playback_time,
void *user_arg)
{
struct cras_hotword_handle *handle;
handle = (struct cras_hotword_handle *)user_arg;
if (handle->trigger_cb)
handle->trigger_cb(client, handle, handle->user_data);
return 0;
}
static int hotword_err_cb(struct cras_client *client,
cras_stream_id_t stream_id,
int error,
void *user_arg)
{
struct cras_hotword_handle *handle;
handle = (struct cras_hotword_handle *)user_arg;
if (handle->err_cb)
handle->err_cb(client, handle, error, handle->user_data);
return 0;
}
int cras_client_enable_hotword_callback(struct cras_client *client,
void *user_data,
cras_hotword_trigger_cb_t trigger_cb,
cras_hotword_error_cb_t err_cb,
struct cras_hotword_handle **handle_out)
{
struct cras_hotword_handle *handle;
int ret = 0;
if (!client)
return -EINVAL;
handle = (struct cras_hotword_handle *)calloc(1, sizeof(*handle));
if (!handle)
return -ENOMEM;
handle->format = cras_audio_format_create(SND_PCM_FORMAT_S16_LE,
HOTWORD_FRAME_RATE, 1);
if (!handle->format) {
ret = -ENOMEM;
goto cleanup;
}
handle->params = cras_client_unified_params_create(
CRAS_STREAM_INPUT,
HOTWORD_BLOCK_SIZE,
CRAS_STREAM_TYPE_DEFAULT,
HOTWORD_STREAM | TRIGGER_ONLY,
(void *)handle,
hotword_read_cb,
hotword_err_cb,
handle->format);
if (!handle->params) {
ret = -ENOMEM;
goto cleanup_format;
}
handle->trigger_cb = trigger_cb;
handle->err_cb = err_cb;
handle->user_data = user_data;
ret = cras_client_add_stream(client, &handle->stream_id,
handle->params);
if (ret)
goto cleanup_params;
*handle_out = handle;
return 0;
cleanup_params:
cras_client_stream_params_destroy(handle->params);
cleanup_format:
cras_audio_format_destroy(handle->format);
cleanup:
free(handle);
return ret;
}
int cras_client_disable_hotword_callback(struct cras_client *client,
struct cras_hotword_handle *handle)
{
if (!client || !handle)
return -EINVAL;
cras_client_rm_stream(client, handle->stream_id);
cras_audio_format_destroy(handle->format);
cras_client_stream_params_destroy(handle->params);
free(handle);
return 0;
}