| /* 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 a pointer to the current read buffer */ |
| static inline |
| uint8_t *cras_shm_get_curr_read_buffer(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) + |
| cras_shm_check_read_offset(shm, shm->area->read_offset[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; |
| |
| write_offset = cras_shm_check_write_offset(shm, |
| shm->area->write_offset[i]); |
| if (frames) |
| *frames = limit_frames - (write_offset / frame_bytes); |
| |
| 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. */ |
| static inline void cras_shm_check_write_overrun(struct cras_audio_shm *shm) |
| { |
| size_t write_buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK; |
| size_t read_buf_idx = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK; |
| |
| if (!shm->area->write_in_progress[write_buf_idx]) { |
| unsigned int used_size = shm->config.used_size; |
| |
| if (write_buf_idx != read_buf_idx) |
| shm->area->num_overruns++; /* Will over-write unread */ |
| |
| 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; |
| } |
| } |
| |
| /* 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 { |
| area->read_offset[buf_idx] = 0; |
| area->write_offset[buf_idx] = 0; |
| if (remainder) { |
| /* Read all of this buffer too. */ |
| 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)); |
| } |
| |
| #endif /* CRAS_SHM_H_ */ |