blob: 55beed2b35555440da983e35ef65d65bcb2df892 [file] [log] [blame]
/* 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.
*/
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <syslog.h>
#include "cras_audio_area.h"
#include "cras_config.h"
#include "cras_messages.h"
#include "cras_rclient.h"
#include "cras_rstream.h"
#include "cras_server_metrics.h"
#include "cras_shm.h"
#include "cras_types.h"
#include "buffer_share.h"
#include "cras_system_state.h"
void cras_rstream_config_init(
struct cras_rclient *client, cras_stream_id_t stream_id,
enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type,
enum CRAS_STREAM_DIRECTION direction, uint32_t dev_idx, uint32_t flags,
uint32_t effects, const struct cras_audio_format *format,
size_t buffer_frames, size_t cb_threshold, int *audio_fd,
int *client_shm_fd, size_t client_shm_size,
struct cras_rstream_config *stream_config)
{
stream_config->stream_id = stream_id;
stream_config->stream_type = stream_type;
stream_config->client_type = client_type;
stream_config->direction = direction;
stream_config->dev_idx = dev_idx;
stream_config->flags = flags;
stream_config->effects = effects;
stream_config->format = format;
stream_config->buffer_frames = buffer_frames;
stream_config->cb_threshold = cb_threshold;
stream_config->audio_fd = *audio_fd;
*audio_fd = -1;
stream_config->client_shm_fd = *client_shm_fd;
*client_shm_fd = -1;
stream_config->client_shm_size = client_shm_size;
stream_config->client = client;
}
void cras_rstream_config_init_with_message(
struct cras_rclient *client, const struct cras_connect_message *msg,
int *aud_fd, int *client_shm_fd,
const struct cras_audio_format *remote_fmt,
struct cras_rstream_config *stream_config)
{
cras_rstream_config_init(client, msg->stream_id, msg->stream_type,
msg->client_type, msg->direction, msg->dev_idx,
msg->flags, msg->effects, remote_fmt,
msg->buffer_frames, msg->cb_threshold, aud_fd,
client_shm_fd, msg->client_shm_size,
stream_config);
}
void cras_rstream_config_cleanup(struct cras_rstream_config *stream_config)
{
if (stream_config->audio_fd >= 0)
close(stream_config->audio_fd);
if (stream_config->client_shm_fd >= 0)
close(stream_config->client_shm_fd);
}
/* Setup the shared memory area used for audio samples. client_shm_fd must be
* closed after calling this function.
*/
static inline int setup_shm_area(struct cras_rstream *stream, int client_shm_fd,
size_t client_shm_size)
{
const struct cras_audio_format *fmt = &stream->format;
char header_name[NAME_MAX];
char samples_name[NAME_MAX];
struct cras_shm_info header_info, samples_info;
uint32_t frame_bytes, used_size;
int rc;
if (stream->shm) {
/* already setup */
return -EEXIST;
}
snprintf(header_name, sizeof(header_name),
"/cras-%d-stream-%08x-header", getpid(), stream->stream_id);
rc = cras_shm_info_init(header_name, cras_shm_header_size(),
&header_info);
if (rc)
return rc;
frame_bytes = snd_pcm_format_physical_width(fmt->format) / 8 *
fmt->num_channels;
used_size = stream->buffer_frames * frame_bytes;
if (client_shm_fd >= 0 && client_shm_size > 0) {
rc = cras_shm_info_init_with_fd(client_shm_fd, client_shm_size,
&samples_info);
} else {
snprintf(samples_name, sizeof(samples_name),
"/cras-%d-stream-%08x-samples", getpid(),
stream->stream_id);
rc = cras_shm_info_init(
samples_name,
cras_shm_calculate_samples_size(used_size),
&samples_info);
}
if (rc) {
cras_shm_info_cleanup(&header_info);
return rc;
}
int samples_prot = 0;
if (stream->direction == CRAS_STREAM_OUTPUT)
samples_prot = PROT_READ;
else
samples_prot = PROT_WRITE;
rc = cras_audio_shm_create(&header_info, &samples_info, samples_prot,
&stream->shm);
if (rc)
return rc;
cras_shm_set_frame_bytes(stream->shm, frame_bytes);
cras_shm_set_used_size(stream->shm, used_size);
stream->audio_area =
cras_audio_area_create(stream->format.num_channels);
cras_audio_area_config_channels(stream->audio_area, &stream->format);
return 0;
}
static inline int buffer_meets_size_limit(size_t buffer_size, size_t rate)
{
return buffer_size > (CRAS_MIN_BUFFER_TIME_IN_US * rate) / 1000000;
}
/* Verifies that the given stream parameters are valid. */
static int verify_rstream_parameters(enum CRAS_STREAM_DIRECTION direction,
const struct cras_audio_format *format,
enum CRAS_STREAM_TYPE stream_type,
size_t buffer_frames, size_t cb_threshold,
int client_shm_fd, size_t client_shm_size,
struct cras_rclient *client,
struct cras_rstream **stream_out)
{
if (!buffer_meets_size_limit(buffer_frames, format->frame_rate)) {
syslog(LOG_ERR, "rstream: invalid buffer_frames %zu\n",
buffer_frames);
return -EINVAL;
}
if (stream_out == NULL) {
syslog(LOG_ERR, "rstream: stream_out can't be NULL\n");
return -EINVAL;
}
if (format == NULL) {
syslog(LOG_ERR, "rstream: format can't be NULL\n");
return -EINVAL;
}
if ((format->format != SND_PCM_FORMAT_S16_LE) &&
(format->format != SND_PCM_FORMAT_S32_LE) &&
(format->format != SND_PCM_FORMAT_U8) &&
(format->format != SND_PCM_FORMAT_S24_LE)) {
syslog(LOG_ERR, "rstream: format %d not supported\n",
format->format);
return -EINVAL;
}
if (direction != CRAS_STREAM_OUTPUT && direction != CRAS_STREAM_INPUT) {
syslog(LOG_ERR, "rstream: Invalid direction.\n");
return -EINVAL;
}
if (stream_type < CRAS_STREAM_TYPE_DEFAULT ||
stream_type >= CRAS_STREAM_NUM_TYPES) {
syslog(LOG_ERR, "rstream: Invalid stream type.\n");
return -EINVAL;
}
if (!buffer_meets_size_limit(cb_threshold, format->frame_rate)) {
syslog(LOG_ERR, "rstream: cb_threshold too low\n");
return -EINVAL;
}
if ((client_shm_size > 0 && client_shm_fd < 0) ||
(client_shm_size == 0 && client_shm_fd >= 0)) {
syslog(LOG_ERR, "rstream: invalid client-provided shm info\n");
return -EINVAL;
}
return 0;
}
/*
* Setting pending reply is only needed inside this module.
*/
static void set_pending_reply(struct cras_rstream *stream)
{
cras_shm_set_callback_pending(stream->shm, 1);
}
/*
* Clearing pending reply is only needed inside this module.
*/
static void clear_pending_reply(struct cras_rstream *stream)
{
cras_shm_set_callback_pending(stream->shm, 0);
}
/*
* Reads one response of audio request from client.
* Args:
* stream[in]: A pointer to cras_rstream.
* msg[out]: A pointer to audio_message to hold the message.
* Returns:
* Number of bytes read from the socket.
* A negative error code if read fails or the message from client
* has errors.
*/
static int get_audio_request_reply(const struct cras_rstream *stream,
struct audio_message *msg)
{
int rc;
rc = read(stream->fd, msg, sizeof(*msg));
if (rc < 0)
return -errno;
if (rc == 0)
return rc;
if (msg->error < 0)
return msg->error;
return rc;
}
/*
* Reads and handles one audio message from client.
* Returns:
* Number of bytes read from the socket.
* A negative error code if read fails or the message from client
* has errors.
*/
static int read_and_handle_client_message(struct cras_rstream *stream)
{
struct audio_message msg;
int rc;
rc = get_audio_request_reply(stream, &msg);
if (rc <= 0) {
syslog(LOG_ERR, "Got error from client: rc: %d", rc);
clear_pending_reply(stream);
return rc;
}
/*
* Got client reply that data in the input stream is captured.
*/
if (stream->direction == CRAS_STREAM_INPUT &&
msg.id == AUDIO_MESSAGE_DATA_CAPTURED) {
clear_pending_reply(stream);
}
/*
* Got client reply that data for output stream is ready in shm.
*/
if (stream->direction == CRAS_STREAM_OUTPUT &&
msg.id == AUDIO_MESSAGE_DATA_READY) {
clear_pending_reply(stream);
}
return rc;
}
/* Exported functions */
int cras_rstream_create(struct cras_rstream_config *config,
struct cras_rstream **stream_out)
{
struct cras_rstream *stream;
int rc;
rc = verify_rstream_parameters(
config->direction, config->format, config->stream_type,
config->buffer_frames, config->cb_threshold,
config->client_shm_fd, config->client_shm_size, config->client,
stream_out);
if (rc < 0)
return rc;
stream = calloc(1, sizeof(*stream));
if (stream == NULL)
return -ENOMEM;
stream->stream_id = config->stream_id;
stream->stream_type = config->stream_type;
stream->client_type = config->client_type;
stream->direction = config->direction;
stream->flags = config->flags;
stream->format = *config->format;
stream->buffer_frames = config->buffer_frames;
stream->cb_threshold = config->cb_threshold;
stream->client = config->client;
stream->shm = NULL;
stream->master_dev.dev_id = NO_DEVICE;
stream->master_dev.dev_ptr = NULL;
stream->num_missed_cb = 0;
stream->is_pinned = (config->dev_idx != NO_DEVICE);
stream->pinned_dev_idx = config->dev_idx;
rc = setup_shm_area(stream, config->client_shm_fd,
config->client_shm_size);
if (rc < 0) {
syslog(LOG_ERR, "failed to setup shm %d\n", rc);
free(stream);
return rc;
}
stream->fd = config->audio_fd;
config->audio_fd = -1;
stream->buf_state = buffer_share_create(stream->buffer_frames);
stream->apm_list =
(stream->direction == CRAS_STREAM_INPUT) ?
cras_apm_list_create(stream, config->effects) :
NULL;
syslog(LOG_DEBUG, "stream %x frames %zu, cb_thresh %zu",
config->stream_id, config->buffer_frames, config->cb_threshold);
*stream_out = stream;
cras_system_state_stream_added(stream->direction);
clock_gettime(CLOCK_MONOTONIC_RAW, &stream->start_ts);
return 0;
}
void cras_rstream_destroy(struct cras_rstream *stream)
{
cras_server_metrics_missed_cb_frequency(stream);
cras_system_state_stream_removed(stream->direction);
close(stream->fd);
cras_audio_shm_destroy(stream->shm);
cras_audio_area_destroy(stream->audio_area);
buffer_share_destroy(stream->buf_state);
if (stream->apm_list)
cras_apm_list_destroy(stream->apm_list);
free(stream);
}
unsigned int cras_rstream_get_effects(const struct cras_rstream *stream)
{
return stream->apm_list ? cras_apm_list_get_effects(stream->apm_list) :
0;
}
struct cras_audio_format *
cras_rstream_post_processing_format(const struct cras_rstream *stream,
void *dev_ptr)
{
struct cras_apm *apm;
if (NULL == stream->apm_list)
return NULL;
apm = cras_apm_list_get(stream->apm_list, dev_ptr);
if (NULL == apm)
return NULL;
return cras_apm_list_get_format(apm);
}
void cras_rstream_record_fetch_interval(struct cras_rstream *rstream,
const struct timespec *now)
{
struct timespec ts;
if (rstream->last_fetch_ts.tv_sec || rstream->last_fetch_ts.tv_nsec) {
subtract_timespecs(now, &rstream->last_fetch_ts, &ts);
if (timespec_after(&ts, &rstream->longest_fetch_interval))
rstream->longest_fetch_interval = ts;
}
}
static void init_audio_message(struct audio_message *msg,
enum CRAS_AUDIO_MESSAGE_ID id, uint32_t frames)
{
memset(msg, 0, sizeof(*msg));
msg->id = id;
msg->frames = frames;
}
int cras_rstream_request_audio(struct cras_rstream *stream,
const struct timespec *now)
{
struct audio_message msg;
int rc;
/* Only request samples from output streams. */
if (stream->direction != CRAS_STREAM_OUTPUT)
return 0;
stream->last_fetch_ts = *now;
init_audio_message(&msg, AUDIO_MESSAGE_REQUEST_DATA,
stream->cb_threshold);
rc = write(stream->fd, &msg, sizeof(msg));
if (rc < 0)
return -errno;
set_pending_reply(stream);
return rc;
}
int cras_rstream_audio_ready(struct cras_rstream *stream, size_t count)
{
struct audio_message msg;
int rc;
cras_shm_buffer_write_complete(stream->shm);
/* Mark shm as used. */
if (stream_is_server_only(stream)) {
cras_shm_buffer_read_current(stream->shm, count);
return 0;
}
init_audio_message(&msg, AUDIO_MESSAGE_DATA_READY, count);
rc = write(stream->fd, &msg, sizeof(msg));
if (rc < 0)
return -errno;
set_pending_reply(stream);
return rc;
}
void cras_rstream_dev_attach(struct cras_rstream *rstream, unsigned int dev_id,
void *dev_ptr)
{
if (buffer_share_add_id(rstream->buf_state, dev_id, dev_ptr) == 0)
rstream->num_attached_devs++;
/* TODO(hychao): Handle master device assignment for complicated
* routing case.
*/
if (rstream->master_dev.dev_id == NO_DEVICE) {
rstream->master_dev.dev_id = dev_id;
rstream->master_dev.dev_ptr = dev_ptr;
}
}
void cras_rstream_dev_detach(struct cras_rstream *rstream, unsigned int dev_id)
{
if (buffer_share_rm_id(rstream->buf_state, dev_id) == 0)
rstream->num_attached_devs--;
if (rstream->master_dev.dev_id == dev_id) {
int i;
struct id_offset *o;
/* Choose the first device id as master. */
rstream->master_dev.dev_id = NO_DEVICE;
rstream->master_dev.dev_ptr = NULL;
for (i = 0; i < rstream->buf_state->id_sz; i++) {
o = &rstream->buf_state->wr_idx[i];
if (o->used) {
rstream->master_dev.dev_id = o->id;
rstream->master_dev.dev_ptr = o->data;
break;
}
}
}
}
void cras_rstream_dev_offset_update(struct cras_rstream *rstream,
unsigned int frames, unsigned int dev_id)
{
buffer_share_offset_update(rstream->buf_state, dev_id, frames);
}
void cras_rstream_update_input_write_pointer(struct cras_rstream *rstream)
{
unsigned int nwritten =
buffer_share_get_new_write_point(rstream->buf_state);
cras_shm_buffer_written(rstream->shm, nwritten);
}
void cras_rstream_update_output_read_pointer(struct cras_rstream *rstream)
{
unsigned int nwritten =
buffer_share_get_new_write_point(rstream->buf_state);
cras_shm_buffer_read(rstream->shm, nwritten);
}
unsigned int cras_rstream_dev_offset(const struct cras_rstream *rstream,
unsigned int dev_id)
{
return buffer_share_id_offset(rstream->buf_state, dev_id);
}
void cras_rstream_update_queued_frames(struct cras_rstream *rstream)
{
rstream->queued_frames =
MIN(cras_shm_get_frames(rstream->shm), rstream->buffer_frames);
}
unsigned int cras_rstream_playable_frames(struct cras_rstream *rstream,
unsigned int dev_id)
{
return rstream->queued_frames -
cras_rstream_dev_offset(rstream, dev_id);
}
float cras_rstream_get_volume_scaler(struct cras_rstream *rstream)
{
return cras_shm_get_volume_scaler(rstream->shm);
}
uint8_t *cras_rstream_get_readable_frames(struct cras_rstream *rstream,
unsigned int offset, size_t *frames)
{
return cras_shm_get_readable_frames(rstream->shm, offset, frames);
}
int cras_rstream_get_mute(const struct cras_rstream *rstream)
{
return cras_shm_get_mute(rstream->shm);
}
int cras_rstream_is_pending_reply(const struct cras_rstream *stream)
{
return cras_shm_callback_pending(stream->shm);
}
int cras_rstream_flush_old_audio_messages(struct cras_rstream *stream)
{
struct pollfd pollfd;
int err;
if (!stream->fd)
return 0;
if (stream_is_server_only(stream))
return 0;
pollfd.fd = stream->fd;
pollfd.events = POLLIN;
do {
err = poll(&pollfd, 1, 0);
if (pollfd.revents & POLLIN) {
err = read_and_handle_client_message(stream);
}
} while (err > 0);
return 0;
}