C++程序  |  544行  |  16.74 KB

/* 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.
 */

#ifndef CRAS_SHM_H_
#define CRAS_SHM_H_

#include <assert.h>
#include <stdint.h>
#include <sys/param.h>

#include "cras_types.h"
#include "cras_util.h"

#define CRAS_NUM_SHM_BUFFERS 2U /* double buffer */
#define CRAS_SHM_BUFFERS_MASK (CRAS_NUM_SHM_BUFFERS - 1)

/* Configuration of the shm area.
 *
 *  used_size - The size in bytes of the sample area being actively used.
 *  frame_bytes - The size of each frame in bytes.
 */
struct __attribute__ ((__packed__)) cras_audio_shm_config {
	uint32_t used_size;
	uint32_t frame_bytes;
};

/* Structure that is shared as shm between client and server.
 *
 *  config - Size config data.  A copy of the config shared with clients.
 *  read_buf_idx - index of the current buffer to read from (0 or 1 if double
 *    buffered).
 *  write_buf_idx - index of the current buffer to write to (0 or 1 if double
 *    buffered).
 *  read_offset - offset of the next sample to read (one per buffer).
 *  write_offset - offset of the next sample to write (one per buffer).
 *  write_in_progress - non-zero when a write is in progress.
 *  volume_scaler - volume scaling factor (0.0-1.0).
 *  muted - bool, true if stream should be muted.
 *  num_overruns - Starting at 0 this is incremented very time data is over
 *    written because too much accumulated before a read.
 *  ts - For capture, the time stamp of the next sample at read_index.  For
 *    playback, this is the time that the next sample written will be played.
 *    This is only valid in audio callbacks.
 *  samples - Audio data - a double buffered area that is used to exchange
 *    audio samples.
 */
struct __attribute__ ((__packed__)) cras_audio_shm_area {
	struct cras_audio_shm_config config;
	uint32_t read_buf_idx; /* use buffer A or B */
	uint32_t write_buf_idx;
	uint32_t read_offset[CRAS_NUM_SHM_BUFFERS];
	uint32_t write_offset[CRAS_NUM_SHM_BUFFERS];
	int32_t write_in_progress[CRAS_NUM_SHM_BUFFERS];
	float volume_scaler;
	int32_t mute;
	int32_t callback_pending;
	uint32_t num_overruns;
	struct cras_timespec ts;
	uint8_t samples[];
};

/* Structure that holds the config for and a pointer to the audio shm area.
 *
 *  config - Size config data, kept separate so it can be checked.
 *  area - Acutal shm region that is shared.
 */
struct cras_audio_shm {
	struct cras_audio_shm_config config;
	struct cras_audio_shm_area *area;
};

/* Get a pointer to the buffer at idx. */
static inline uint8_t *cras_shm_buff_for_idx(const struct cras_audio_shm *shm,
					     size_t idx)
{
	assert_on_compile_is_power_of_2(CRAS_NUM_SHM_BUFFERS);
	idx = idx & CRAS_SHM_BUFFERS_MASK;
	return shm->area->samples + shm->config.used_size * idx;
}

/* Limit a read offset to within the buffer size. */
static inline
unsigned cras_shm_check_read_offset(const struct cras_audio_shm *shm,
				    unsigned offset)
{
	/* The offset is allowed to be the total size, indicating that the
	 * buffer is full. If read pointer is invalid assume it is at the
	 * beginning. */
	if (offset > shm->config.used_size)
		return 0;
	return offset;
}

/* Limit a write offset to within the buffer size. */
static inline
unsigned cras_shm_check_write_offset(const struct cras_audio_shm *shm,
				     unsigned offset)
{
	/* The offset is allowed to be the total size, indicating that the
	 * buffer is full. If write pointer is invalid assume it is at the
	 * end. */
	if (offset > shm->config.used_size)
		return shm->config.used_size;
	return offset;
}

/* Get the number of frames readable in current read buffer */
static inline
unsigned cras_shm_get_curr_read_frames(const struct cras_audio_shm *shm)
{
	unsigned i = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
	unsigned read_offset, write_offset;

	read_offset =
		cras_shm_check_read_offset(shm, shm->area->read_offset[i]);
	write_offset =
		cras_shm_check_write_offset(shm, shm->area->write_offset[i]);

	if (read_offset > write_offset)
		return 0;
	else
		return (write_offset - read_offset) / shm->config.frame_bytes;
}

/* Get the base of the current read buffer. */
static inline
uint8_t *cras_shm_get_read_buffer_base(const struct cras_audio_shm *shm)
{
	unsigned i = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
	return cras_shm_buff_for_idx(shm, i);
}

/* Get the base of the current write buffer. */
static inline
uint8_t *cras_shm_get_write_buffer_base(const struct cras_audio_shm *shm)
{
	unsigned i = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	return cras_shm_buff_for_idx(shm, i);
}

/* Get a pointer to the next buffer to write */
static inline
uint8_t *cras_shm_get_writeable_frames(const struct cras_audio_shm *shm,
				       unsigned limit_frames,
				       unsigned *frames)
{
	unsigned i = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;
	unsigned write_offset;
	const unsigned frame_bytes = shm->config.frame_bytes;
	unsigned written;

	write_offset = cras_shm_check_write_offset(shm,
						   shm->area->write_offset[i]);
	written = write_offset / frame_bytes;
	if (frames) {
		if (limit_frames >= written)
			*frames = limit_frames - written;
		else
			*frames = 0;
	}

	return cras_shm_buff_for_idx(shm, i) + write_offset;
}

/* Get a pointer to the current read buffer plus an offset.  The offset might be
 * in the next buffer. 'frames' is filled with the number of frames that can be
 * copied from the returned buffer.
 */
static inline
uint8_t *cras_shm_get_readable_frames(const struct cras_audio_shm *shm,
				      size_t offset,
				      size_t *frames)
{
	unsigned buf_idx = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
	unsigned read_offset, write_offset, final_offset;

	assert(frames != NULL);

	read_offset =
		cras_shm_check_read_offset(shm,
					   shm->area->read_offset[buf_idx]);
	write_offset =
		cras_shm_check_write_offset(shm,
					    shm->area->write_offset[buf_idx]);
	final_offset = read_offset + offset * shm->config.frame_bytes;
	if (final_offset >= write_offset) {
		final_offset -= write_offset;
		assert_on_compile_is_power_of_2(CRAS_NUM_SHM_BUFFERS);
		buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
		write_offset = cras_shm_check_write_offset(
				shm, shm->area->write_offset[buf_idx]);
	}
	if (final_offset >= write_offset) {
		/* Past end of samples. */
		*frames = 0;
		return NULL;
	}
	*frames = (write_offset - final_offset) / shm->config.frame_bytes;
	return cras_shm_buff_for_idx(shm, buf_idx) + final_offset;
}

/* How many bytes are queued? */
static inline size_t cras_shm_get_bytes_queued(const struct cras_audio_shm *shm)
{
	size_t total, i;
	const unsigned used_size = shm->config.used_size;

	total = 0;
	for (i = 0; i < CRAS_NUM_SHM_BUFFERS; i++) {
		unsigned read_offset, write_offset;

		read_offset = MIN(shm->area->read_offset[i], used_size);
		write_offset = MIN(shm->area->write_offset[i], used_size);

		if (write_offset > read_offset)
			total += write_offset - read_offset;
	}
	return total;
}

/* How many frames are queued? */
static inline int cras_shm_get_frames(const struct cras_audio_shm *shm)
{
	size_t bytes;

	bytes = cras_shm_get_bytes_queued(shm);
	if (bytes % shm->config.frame_bytes != 0)
		return -EIO;
	return bytes / shm->config.frame_bytes;
}

/* How many frames in the current buffer? */
static inline
size_t cras_shm_get_frames_in_curr_buffer(const struct cras_audio_shm *shm)
{
	size_t buf_idx = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
	unsigned read_offset, write_offset;
	const unsigned used_size = shm->config.used_size;

	read_offset = MIN(shm->area->read_offset[buf_idx], used_size);
	write_offset = MIN(shm->area->write_offset[buf_idx], used_size);

	if (write_offset <= read_offset)
		return 0;

	return (write_offset - read_offset) / shm->config.frame_bytes;
}

/* Return 1 if there is an empty buffer in the list. */
static inline int cras_shm_is_buffer_available(const struct cras_audio_shm *shm)
{
	size_t buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	return (shm->area->write_offset[buf_idx] == 0);
}

/* How many are available to be written? */
static inline
size_t cras_shm_get_num_writeable(const struct cras_audio_shm *shm)
{
	/* Not allowed to write to a buffer twice. */
	if (!cras_shm_is_buffer_available(shm))
		return 0;

	return shm->config.used_size / shm->config.frame_bytes;
}

/* Flags an overrun if writing would cause one and reset the write offset.
 * Return 1 if overrun happens, otherwise return 0. */
static inline int cras_shm_check_write_overrun(struct cras_audio_shm *shm)
{
	int ret = 0;
	size_t write_buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	if (!shm->area->write_in_progress[write_buf_idx]) {
		unsigned int used_size = shm->config.used_size;

		if (shm->area->write_offset[write_buf_idx]) {
			shm->area->num_overruns++; /* Will over-write unread */
			ret = 1;
		}

		memset(cras_shm_buff_for_idx(shm, write_buf_idx), 0, used_size);

		shm->area->write_in_progress[write_buf_idx] = 1;
		shm->area->write_offset[write_buf_idx] = 0;
	}
	return ret;
}

/* Increment the write pointer for the current buffer. */
static inline
void cras_shm_buffer_written(struct cras_audio_shm *shm, size_t frames)
{
	size_t buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	if (frames == 0)
		return;

	shm->area->write_offset[buf_idx] += frames * shm->config.frame_bytes;
	shm->area->read_offset[buf_idx] = 0;
}

/* Returns the number of frames that have been written to the current buffer. */
static inline
unsigned int cras_shm_frames_written(const struct cras_audio_shm *shm)
{
	size_t buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	return shm->area->write_offset[buf_idx] / shm->config.frame_bytes;
}

/* Signals the writing to this buffer is complete and moves to the next one. */
static inline void cras_shm_buffer_write_complete(struct cras_audio_shm *shm)
{
	size_t buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	shm->area->write_in_progress[buf_idx] = 0;

	assert_on_compile_is_power_of_2(CRAS_NUM_SHM_BUFFERS);
	buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
	shm->area->write_buf_idx = buf_idx;
}

/* Set the write pointer for the current buffer and complete the write. */
static inline
void cras_shm_buffer_written_start(struct cras_audio_shm *shm, size_t frames)
{
	size_t buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;

	shm->area->write_offset[buf_idx] = frames * shm->config.frame_bytes;
	shm->area->read_offset[buf_idx] = 0;
	cras_shm_buffer_write_complete(shm);
}

/* Increment the read pointer.  If it goes past the write pointer for this
 * buffer, move to the next buffer. */
static inline
void cras_shm_buffer_read(struct cras_audio_shm *shm, size_t frames)
{
	size_t buf_idx = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
	size_t remainder;
	struct cras_audio_shm_area *area = shm->area;
	struct cras_audio_shm_config *config = &shm->config;

	if (frames == 0)
		return;

	area->read_offset[buf_idx] += frames * config->frame_bytes;
	if (area->read_offset[buf_idx] >= area->write_offset[buf_idx]) {
		remainder = area->read_offset[buf_idx] -
				area->write_offset[buf_idx];
		area->read_offset[buf_idx] = 0;
		area->write_offset[buf_idx] = 0;
		assert_on_compile_is_power_of_2(CRAS_NUM_SHM_BUFFERS);
		buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
		if (remainder < area->write_offset[buf_idx]) {
			area->read_offset[buf_idx] = remainder;
		} else if (remainder) {
			/* Read all of this buffer too. */
			area->write_offset[buf_idx] = 0;
			buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
		}
		area->read_buf_idx = buf_idx;
	}
}

/* Read from the current buffer. This is similar to cras_shm_buffer_read(), but
 * it doesn't check for the case we may read from two buffers. */
static inline
void cras_shm_buffer_read_current(struct cras_audio_shm *shm, size_t frames)
{
	size_t buf_idx = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
	struct cras_audio_shm_area *area = shm->area;
	struct cras_audio_shm_config *config = &shm->config;

	area->read_offset[buf_idx] += frames * config->frame_bytes;
	if (area->read_offset[buf_idx] >= area->write_offset[buf_idx]) {
		area->read_offset[buf_idx] = 0;
		area->write_offset[buf_idx] = 0;
		buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
		area->read_buf_idx = buf_idx;
	}
}

/* Sets the volume for the stream.  The volume level is a scaling factor that
 * will be applied to the stream before mixing. */
static inline
void cras_shm_set_volume_scaler(struct cras_audio_shm *shm, float volume_scaler)
{
	volume_scaler = MAX(volume_scaler, 0.0);
	shm->area->volume_scaler = MIN(volume_scaler, 1.0);
}

/* Returns the volume of the stream(0.0-1.0). */
static inline float cras_shm_get_volume_scaler(const struct cras_audio_shm *shm)
{
	return shm->area->volume_scaler;
}

/* Indicates that the stream should be muted/unmuted */
static inline void cras_shm_set_mute(struct cras_audio_shm *shm, size_t mute)
{
	shm->area->mute = !!mute;
}

/* Returns the mute state of the stream.  0 if not muted, non-zero if muted. */
static inline size_t cras_shm_get_mute(const struct cras_audio_shm *shm)
{
	return shm->area->mute;
}

/* Sets the size of a frame in bytes. */
static inline void cras_shm_set_frame_bytes(struct cras_audio_shm *shm,
					    unsigned frame_bytes)
{
	shm->config.frame_bytes = frame_bytes;
	if (shm->area)
		shm->area->config.frame_bytes = frame_bytes;
}

/* Returns the size of a frame in bytes. */
static inline unsigned cras_shm_frame_bytes(const struct cras_audio_shm *shm)
{
	return shm->config.frame_bytes;
}

/* Sets if a callback is pending with the client. */
static inline
void cras_shm_set_callback_pending(struct cras_audio_shm *shm, int pending)
{
	shm->area->callback_pending = !!pending;
}

/* Returns non-zero if a callback is pending for this shm region. */
static inline int cras_shm_callback_pending(const struct cras_audio_shm *shm)
{
	return shm->area->callback_pending;
}

/* Sets the used_size of the shm region.  This is the maximum number of bytes
 * that is exchanged each time a buffer is passed from client to server.
 */
static inline
void cras_shm_set_used_size(struct cras_audio_shm *shm, unsigned used_size)
{
	shm->config.used_size = used_size;
	if (shm->area)
		shm->area->config.used_size = used_size;
}

/* Returns the used size of the shm region in bytes. */
static inline unsigned cras_shm_used_size(const struct cras_audio_shm *shm)
{
	return shm->config.used_size;
}

/* Returns the used size of the shm region in frames. */
static inline unsigned cras_shm_used_frames(const struct cras_audio_shm *shm)
{
	return shm->config.used_size / shm->config.frame_bytes;
}

/* Returns the total size of the shared memory region. */
static inline unsigned cras_shm_total_size(const struct cras_audio_shm *shm)
{
	return cras_shm_used_size(shm) * CRAS_NUM_SHM_BUFFERS +
			sizeof(*shm->area);
}

/* Gets the counter of over-runs. */
static inline
unsigned cras_shm_num_overruns(const struct cras_audio_shm *shm)
{
	return shm->area->num_overruns;
}

/* Copy the config from the shm region to the local config.  Used by clients
 * when initially setting up the region.
 */
static inline void cras_shm_copy_shared_config(struct cras_audio_shm *shm)
{
	memcpy(&shm->config, &shm->area->config, sizeof(shm->config));
}

/* Open a read/write shared memory area with the given name.
 * Args:
 *    name - Name of the shared-memory area.
 *    size - Size of the shared-memory area.
 * Returns:
 *    >= 0 file descriptor value, or negative errno value on error.
 */
int cras_shm_open_rw (const char *name, size_t size);

/* Reopen an existing shared memory area read-only.
 * Args:
 *    name - Name of the shared-memory area.
 *    fd - Existing file descriptor.
 * Returns:
 *    >= 0 new file descriptor value, or negative errno value on error.
 */
int cras_shm_reopen_ro (const char *name, int fd);

/* Close and delete a shared memory area.
 * Args:
 *    name - Name of the shared-memory area.
 *    fd - Existing file descriptor.
 * Returns:
 *    >= 0 new file descriptor value, or negative errno value on error.
 */
void cras_shm_close_unlink (const char *name, int fd);

/*
 * Configure shared memory for the system state.
 * Args:
 *    name - Name of the shared-memory area.
 *    mmap_size - Amount of shared memor to map.
 *    rw_fd_out - Filled with the RW fd for the shm region.
 *    ro_fd_out - Filled with the RO fd for the shm region.
 * Returns a pointer to the new shared memory region. Or NULL on error.
 */
void *cras_shm_setup(const char *name,
		     size_t mmap_size,
		     int *rw_fd_out,
		     int *ro_fd_out);

#ifdef CRAS_SELINUX
/*
 * Wrapper around selinux_restorecon(). This is helpful in unit tests because
 * we can mock out the selinux_restorecon() behaviour there. That is required
 * because selinux_restorecon() would fail in the unit tests, since there
 * is no file_contexts file.
 * Args:
 *    pathname - Name of the file on which to run restorecon
 * Returns 0 on success, otherwise -1 and errno is set appropriately.
 */
int cras_selinux_restorecon(const char *pathname);
#endif

#endif /* CRAS_SHM_H_ */