/* * Copyright (c) 2013 Jan Vesely * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup audio * @brief HelenOS sound server * @{ */ /** @file */ #include #include #include "audio_data.h" #include "log.h" /** * Create reference counted buffer out of ordinary data buffer. * @param data audio buffer. The memory passed will be freed eventually. * @param size Size of the @p data buffer. * @param format audio data format. * @return pointer to valid audio data structure, NULL on failure. */ audio_data_t *audio_data_create(void *data, size_t size, pcm_format_t format) { audio_data_t *adata = malloc(sizeof(audio_data_t) + size); if (adata) { unsigned overflow = size % pcm_format_frame_size(&format); if (overflow) log_warning("Data not a multiple of frame size, " "clipping."); adata->data = data; adata->size = size - overflow; adata->format = format; refcount_init(&adata->refcount); } return adata; } /** * Get a new reference to the audio data buffer. * @param adata The audio data buffer. */ void audio_data_addref(audio_data_t *adata) { assert(adata); refcount_up(&adata->refcount); } /** * Release a reference to the audio data buffer. * @param adata The audio data buffer. */ void audio_data_unref(audio_data_t *adata) { assert(adata); if (refcount_down(&adata->refcount)) { free(adata->data); free(adata); } } /* Data link helpers */ /** Audio data buffer list helper structure. */ typedef struct { link_t link; audio_data_t *adata; size_t position; } audio_data_link_t; /** List instance helper function. * @param l link * @return valid pointer to data link structure, NULL on failure. */ static inline audio_data_link_t *audio_data_link_list_instance(link_t *l) { return l ? list_get_instance(l, audio_data_link_t, link) : NULL; } /** * Create a new audio data link. * @param adata Audio data to store. * @return Valid pointer to a new audio data link structure, NULL on failure. */ static audio_data_link_t *audio_data_link_create(audio_data_t *adata) { assert(adata); audio_data_link_t *link = malloc(sizeof(audio_data_link_t)); if (link) { audio_data_addref(adata); link->adata = adata; link->position = 0; } return link; } /** * Destroy audio data link. * @param link The link to destroy. * * Releases data reference. */ static void audio_data_link_destroy(audio_data_link_t *link) { assert(link); assert(!link_in_use(&link->link)); audio_data_unref(link->adata); free(link); } /** * Data link buffer start helper function. * @param alink audio data link * @return pointer to the beginning of data buffer. */ static inline const void *audio_data_link_start(audio_data_link_t *alink) { assert(alink); assert(alink->adata); return alink->adata->data + alink->position; } /** * Data link remaining size getter. * @param alink audio data link * @return Remaining size of valid data in the buffer. */ static inline size_t audio_data_link_remain_size(audio_data_link_t *alink) { assert(alink); assert(alink->adata); assert(alink->position <= alink->adata->size); return alink->adata->size - alink->position; } /** * Data link remaining frames getter. * @param alink audio data link * @return Number of remaining frames in the buffer. */ static inline size_t audio_data_link_available_frames(audio_data_link_t *alink) { assert(alink); assert(alink->adata); return pcm_format_size_to_frames(audio_data_link_remain_size(alink), &alink->adata->format); } /* Audio Pipe */ /** * Initialize audio pipe structure. * @param pipe The pipe structure to initialize. */ void audio_pipe_init(audio_pipe_t *pipe) { assert(pipe); list_initialize(&pipe->list); fibril_mutex_initialize(&pipe->guard); pipe->frames = 0; pipe->bytes = 0; } /** * Destroy all data in a pipe. * @param pipe The audio pipe to clean. */ void audio_pipe_fini(audio_pipe_t *pipe) { assert(pipe); while (!list_empty(&pipe->list)) { audio_data_t *adata = audio_pipe_pop(pipe); audio_data_unref(adata); } } /** * Add new audio data to a pipe. * @param pipe The target pipe. * @param data The data. * @return Error code. */ errno_t audio_pipe_push(audio_pipe_t *pipe, audio_data_t *data) { assert(pipe); assert(data); audio_data_link_t *alink = audio_data_link_create(data); if (!alink) return ENOMEM; fibril_mutex_lock(&pipe->guard); list_append(&alink->link, &pipe->list); pipe->bytes += audio_data_link_remain_size(alink); pipe->frames += audio_data_link_available_frames(alink); fibril_mutex_unlock(&pipe->guard); return EOK; } /** * Retrieve data form a audio pipe. * @param pipe THe target pipe. * @return Valid pointer to audio data, NULL if the pipe was empty. */ audio_data_t *audio_pipe_pop(audio_pipe_t *pipe) { assert(pipe); fibril_mutex_lock(&pipe->guard); audio_data_t *adata = NULL; link_t *l = list_first(&pipe->list); if (l) { audio_data_link_t *alink = audio_data_link_list_instance(l); list_remove(&alink->link); pipe->bytes -= audio_data_link_remain_size(alink); pipe->frames -= audio_data_link_available_frames(alink); adata = alink->adata; audio_data_addref(adata); audio_data_link_destroy(alink); } fibril_mutex_unlock(&pipe->guard); return adata; } /** * Use data store in a pipe and mix it into the provided buffer. * @param pipe The piep that should provide data. * @param data Target buffer. * @param size Target buffer size. * @param format Target data format. * @return Size of the target buffer used. */ size_t audio_pipe_mix_data(audio_pipe_t *pipe, void *data, size_t size, const pcm_format_t *f) { assert(pipe); const size_t dst_frame_size = pcm_format_frame_size(f); size_t needed_frames = pcm_format_size_to_frames(size, f); size_t copied_size = 0; fibril_mutex_lock(&pipe->guard); while (needed_frames > 0 && !list_empty(&pipe->list)) { /* Get first audio chunk */ link_t *l = list_first(&pipe->list); audio_data_link_t *alink = audio_data_link_list_instance(l); /* Get audio chunk metadata */ const size_t src_frame_size = pcm_format_frame_size(&alink->adata->format); const size_t available_frames = audio_data_link_available_frames(alink); const size_t copy_frames = min(available_frames, needed_frames); const size_t dst_copy_size = copy_frames * dst_frame_size; const size_t src_copy_size = copy_frames * src_frame_size; assert(src_copy_size <= audio_data_link_remain_size(alink)); /* Copy audio data */ pcm_format_convert_and_mix(data, dst_copy_size, audio_data_link_start(alink), src_copy_size, &alink->adata->format, f); /* Update values */ needed_frames -= copy_frames; copied_size += dst_copy_size; data += dst_copy_size; alink->position += src_copy_size; pipe->bytes -= src_copy_size; pipe->frames -= copy_frames; if (audio_data_link_remain_size(alink) == 0) { list_remove(&alink->link); audio_data_link_destroy(alink); } else { assert(needed_frames == 0); } } fibril_mutex_unlock(&pipe->guard); return copied_size; } /** * @} */