/* 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_shm.h" #include "cras_types.h" #include "buffer_share.h" #include "cras_system_state.h" /* Configure the shm area for the stream. */ static int setup_shm(struct cras_rstream *stream, struct cras_audio_shm *shm, struct rstream_shm_info *shm_info) { size_t used_size, samples_size, frame_bytes; const struct cras_audio_format *fmt = &stream->format; if (shm->area != NULL) /* already setup */ return -EEXIST; frame_bytes = snd_pcm_format_physical_width(fmt->format) / 8 * fmt->num_channels; used_size = stream->buffer_frames * frame_bytes; samples_size = used_size * CRAS_NUM_SHM_BUFFERS; shm_info->length = sizeof(struct cras_audio_shm_area) + samples_size; snprintf(shm_info->shm_name, sizeof(shm_info->shm_name), "/cras-%d-stream-%08x", getpid(), stream->stream_id); shm_info->shm_fd = cras_shm_open_rw(shm_info->shm_name, shm_info->length); if (shm_info->shm_fd < 0) return shm_info->shm_fd; /* mmap shm. */ shm->area = mmap(NULL, shm_info->length, PROT_READ | PROT_WRITE, MAP_SHARED, shm_info->shm_fd, 0); if (shm->area == (struct cras_audio_shm_area *)-1) { close(shm_info->shm_fd); return errno; } cras_shm_set_volume_scaler(shm, 1.0); /* Set up config and copy to shared area. */ cras_shm_set_frame_bytes(shm, frame_bytes); shm->config.frame_bytes = frame_bytes; cras_shm_set_used_size(shm, used_size); memcpy(&shm->area->config, &shm->config, sizeof(shm->config)); return 0; } /* Setup the shared memory area used for audio samples. */ static inline int setup_shm_area(struct cras_rstream *stream) { int rc; rc = setup_shm(stream, &stream->shm, &stream->shm_info); if (rc) return rc; 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, 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; } return 0; } /* 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, 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->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.area = NULL; stream->master_dev.dev_id = NO_DEVICE; stream->master_dev.dev_ptr = NULL; stream->is_pinned = (config->dev_idx != NO_DEVICE); stream->pinned_dev_idx = config->dev_idx; stream->fd = config->audio_fd; rc = setup_shm_area(stream); if (rc < 0) { syslog(LOG_ERR, "failed to setup shm %d\n", rc); free(stream); return rc; } stream->buf_state = buffer_share_create(stream->buffer_frames); 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); return 0; } void cras_rstream_destroy(struct cras_rstream *stream) { cras_system_state_stream_removed(stream->direction); close(stream->fd); if (stream->shm.area != NULL) { munmap(stream->shm.area, stream->shm_info.length); cras_shm_close_unlink(stream->shm_info.shm_name, stream->shm_info.shm_fd); cras_audio_area_destroy(stream->audio_area); } buffer_share_destroy(stream->buf_state); free(stream); } 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; 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); init_audio_message(&msg, AUDIO_MESSAGE_DATA_READY, count); rc = write(stream->fd, &msg, sizeof(msg)); if (rc < 0) return -errno; return rc; } int cras_rstream_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 (msg.error < 0) return msg.error; return 0; } 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) { struct cras_audio_shm *shm = cras_rstream_input_shm(rstream); unsigned int nwritten = buffer_share_get_new_write_point( rstream->buf_state); cras_shm_buffer_written(shm, nwritten); } void cras_rstream_update_output_read_pointer(struct cras_rstream *rstream) { struct cras_audio_shm *shm = cras_rstream_input_shm(rstream); unsigned int nwritten = buffer_share_get_new_write_point( rstream->buf_state); cras_shm_buffer_read(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) { const struct cras_audio_shm *shm = cras_rstream_output_shm(rstream); rstream->queued_frames = MIN(cras_shm_get_frames(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) { const struct cras_audio_shm *shm = cras_rstream_output_shm(rstream); return cras_shm_get_volume_scaler(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); }