From: Kishore SN Date: Wed, 30 Aug 2017 14:21:35 +0000 (+0530) Subject: Initial porting of TinyAlsa on TizenRT X-Git-Tag: 1.1_Public_Release~188^2~49 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=bcaff75202f6d4c3ba0e3ec0ce23929c5aa104f7;p=rtos%2Ftinyara.git Initial porting of TinyAlsa on TizenRT --- diff --git a/framework/Makefile b/framework/Makefile index f56df3b..b344b70 100644 --- a/framework/Makefile +++ b/framework/Makefile @@ -46,6 +46,10 @@ ifeq ($(CONFIG_WIFI_MANAGER), y) include src$(DELIM)wifi_manager$(DELIM)Make.defs endif +ifeq ($(CONFIG_AUDIO), y) +include src$(DELIM)tinyalsa$(DELIM)Make.defs +endif + AOBJS = $(ASRCS:.S=$(OBJEXT)) COBJS = $(CSRCS:.c=$(OBJEXT)) diff --git a/framework/include/tinyalsa/interval.h b/framework/include/tinyalsa/interval.h new file mode 100644 index 0000000..6de2e13 --- /dev/null +++ b/framework/include/tinyalsa/interval.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * + * Copyright 2017 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* interval.h +** +** Copyright 2011, The Android Open Source Project +** +** 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. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``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 Android Open Source Project 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. +*/ + +#ifndef TINYALSA_INTERVAL_H +#define TINYALSA_INTERVAL_H + +#include +#include +#include + +/** A closed range signed interval. */ + +struct tinyalsa_signed_interval { + /** The maximum value of the interval */ + ssize_t max; + /** The minimum value of the interval */ + ssize_t min; +}; + +/** A closed range unsigned interval. */ + +struct tinyalsa_unsigned_interval { + /** The maximum value of the interval */ + size_t max; + /** The minimum value of the interval */ + size_t min; +}; + +#endif /* TINYALSA_INTERVAL_H */ diff --git a/framework/include/tinyalsa/limits.h b/framework/include/tinyalsa/limits.h new file mode 100644 index 0000000..c45d531 --- /dev/null +++ b/framework/include/tinyalsa/limits.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * + * Copyright 2017 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* limits.h +** +** Copyright 2011, The Android Open Source Project +** +** 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. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``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 Android Open Source Project 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. +*/ + +#ifndef TINYALSA_LIMITS_H +#define TINYALSA_LIMITS_H + +#include + +#include +#include + +#define TINYALSA_SIGNED_INTERVAL_MAX SSIZE_MAX +#define TINYALSA_SIGNED_INTERVAL_MIN SSIZE_MIN + +#define TINYALSA_UNSIGNED_INTERVAL_MAX SIZE_MAX +#define TINYALSA_UNSIGNED_INTERVAL_MIN SIZE_MIN + +#define TINYALSA_CHANNELS_MAX 32U +#define TINYALSA_CHANNELS_MIN 1U + +#define TINYALSA_FRAMES_MAX (ULONG_MAX / (TINYALSA_CHANNELS_MAX * 4)) +#define TINYALSA_FRAMES_MIN 0U + +#if TINYALSA_FRAMES_MAX > TINYALSA_UNSIGNED_INTERVAL_MAX +#error "Frames max exceeds measurable value." +#endif + +#if TINYALSA_FRAMES_MIN < TINYALSA_UNSIGNED_INTERVAL_MIN +#error "Frames min exceeds measurable value." +#endif + +extern const struct tinyalsa_unsigned_interval tinyalsa_channels_limit; + +extern const struct tinyalsa_unsigned_interval tinyalsa_frames_limit; + +#endif /* TINYALSA_LIMITS_H */ diff --git a/framework/include/tinyalsa/tinyalsa.h b/framework/include/tinyalsa/tinyalsa.h new file mode 100755 index 0000000..1299ca0 --- /dev/null +++ b/framework/include/tinyalsa/tinyalsa.h @@ -0,0 +1,455 @@ +/**************************************************************************** + * + * Copyright 2017 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +/* tinyalsa.h +** +** Copyright 2011, The Android Open Source Project +** +** 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. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``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 Android Open Source Project 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. +*/ + + /** + * @ingroup TinyAlsa + * @defgroup TinyAlsa TinyAlsa + * @brief All macros, structures and functions that make up the PCM interface. + * @{ + */ + +#ifndef TINYALSA_PCM_H +#define TINYALSA_PCM_H + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/** A flag that specifies that the PCM is an output. + * May not be bitwise AND'd with @ref PCM_IN. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_OUT 0x00000000 + +/** Specifies that the PCM is an input. + * May not be bitwise AND'd with @ref PCM_OUT. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_IN 0x10000000 + +/** Specifies that the PCM will use mmap read and write methods. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_MMAP 0x00000001 + +/** Specifies no interrupt requests. + * May only be bitwise AND'd with @ref PCM_MMAP. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_NOIRQ 0x00000002 + +/** When set, calls to @ref pcm_write + * for a playback stream will not attempt + * to restart the stream in the case of an + * underflow, but will return -EPIPE instead. + * After the first -EPIPE error, the stream + * is considered to be stopped, and a second + * call to pcm_write will attempt to restart + * the stream. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_NORESTART 0x00000004 + +/** Specifies monotonic timestamps. + * Used in @ref pcm_open. + * @ingroup libtinyalsa-pcm + */ +#define PCM_MONOTONIC 0x00000008 + +/** For inputs, this means the PCM is recording audio samples. + * For outputs, this means the PCM is playing audio samples. + * @ingroup libtinyalsa-pcm + */ +#define PCM_STATE_RUNNING 0x03 + +/** For inputs, this means an overrun occured. + * For outputs, this means an underrun occured. + */ +#define PCM_STATE_XRUN 0x04 + +/** For outputs, this means audio samples are played. + * A PCM is in a draining state when it is coming to a stop. + */ +#define PCM_STATE_DRAINING 0x05 + +/** Means a PCM is suspended. + * @ingroup libtinyalsa-pcm + */ +#define PCM_STATE_SUSPENDED 0x07 + +/** Means a PCM has been disconnected. + * @ingroup libtinyalsa-pcm + */ +#define PCM_STATE_DISCONNECTED 0x08 + +/** Audio sample format of a PCM. + * The first letter specifiers whether the sample is signed or unsigned. + * The letter 'S' means signed. The letter 'U' means unsigned. + * The following number is the amount of bits that the sample occupies in memory. + * Following the underscore, specifiers whether the sample is big endian or little endian. + * The letters 'LE' mean little endian. + * The letters 'BE' mean big endian. + * This enumeration is used in the @ref pcm_config structure. + * @ingroup libtinyalsa-pcm + */ +enum pcm_format { + /** Signed, 8-bit */ + PCM_FORMAT_S8 = 1, + /** Signed 16-bit, little endian */ + PCM_FORMAT_S16_LE = 0, + /** Signed, 16-bit, big endian */ + PCM_FORMAT_S16_BE = 2, + /** Signed, 24-bit (32-bit in memory), little endian */ + PCM_FORMAT_S24_LE, + /** Signed, 24-bit (32-bit in memory), big endian */ + PCM_FORMAT_S24_BE, + /** Signed, 24-bit, little endian */ + PCM_FORMAT_S24_3LE, + /** Signed, 24-bit, big endian */ + PCM_FORMAT_S24_3BE, + /** Signed, 32-bit, little endian */ + PCM_FORMAT_S32_LE, + /** Signed, 32-bit, big endian */ + PCM_FORMAT_S32_BE, + /** Max of the enumeration list, not an actual format. */ + PCM_FORMAT_MAX +}; + +/** A bit mask of 256 bits (32 bytes) that describes some hardware parameters of a PCM */ +struct pcm_mask { + /** bits of the bit mask */ + unsigned int bits[32 / sizeof(unsigned int)]; +}; + +/** Encapsulates the hardware and software parameters of a PCM. + * @ingroup libtinyalsa-pcm + */ +struct pcm_config { + /** The number of channels in a frame */ + unsigned int channels; + /** The number of frames per second */ + unsigned int rate; + /** The number of frames in a period */ + unsigned int period_size; + /** The number of periods in a PCM */ + unsigned int period_count; + /** The sample format of a PCM */ + enum pcm_format format; + /* Values to use for the ALSA start, stop and silence thresholds. Setting + * any one of these values to 0 will cause the default tinyalsa values to be + * used instead. Tinyalsa defaults are as follows. + * + * start_threshold : period_count * period_size + * stop_threshold : period_count * period_size + * silence_threshold : 0 + */ + /** The minimum number of frames required to start the PCM */ + unsigned int start_threshold; + /** The minimum number of frames required to stop the PCM */ + unsigned int stop_threshold; + /** The minimum number of frames to silence the PCM */ + unsigned int silence_threshold; +}; + +/** Enumeration of a PCM's hardware parameters. + * Each of these parameters is either a mask or an interval. + * @ingroup libtinyalsa-pcm + */ +enum pcm_param +{ + /** A mask that represents the type of read or write method available (e.g. interleaved, mmap). */ + PCM_PARAM_ACCESS, + /** A mask that represents the @ref pcm_format available (e.g. @ref PCM_FORMAT_S32_LE) */ + PCM_PARAM_FORMAT, + /** A mask that represents the subformat available */ + PCM_PARAM_SUBFORMAT, + /** An interval representing the range of sample bits available (e.g. 8 to 32) */ + PCM_PARAM_SAMPLE_BITS, + /** An interval representing the range of frame bits available (e.g. 8 to 64) */ + PCM_PARAM_FRAME_BITS, + /** An interval representing the range of channels available (e.g. 1 to 2) */ + PCM_PARAM_CHANNELS, + /** An interval representing the range of rates available (e.g. 44100 to 192000) */ + PCM_PARAM_RATE, + PCM_PARAM_PERIOD_TIME, + /** The number of frames in a period */ + PCM_PARAM_PERIOD_SIZE, + /** The number of bytes in a period */ + PCM_PARAM_PERIOD_BYTES, + /** The number of periods for a PCM */ + PCM_PARAM_PERIODS, + PCM_PARAM_BUFFER_TIME, + PCM_PARAM_BUFFER_SIZE, + PCM_PARAM_BUFFER_BYTES, + PCM_PARAM_TICK_TIME, +}; /* enum pcm_param */ + + +struct pcm; + + +/** +* @brief Opens a PCM for playback or recording. +* +* @param[in] The card that the pcm belongs to. +* @param[in] The device that the pcm belongs to. +* @param[in] flags Specify characteristics and functionality about the pcm. +* @param[in] config The hardware and software parameters to open the PCM with. +* @returns On success, A PCM structure returned. On failure, invalid PCM structure returned. +* @since Tizen RT v1.1 +*/ +struct pcm *pcm_open(unsigned int card, + unsigned int device, + unsigned int flags, + const struct pcm_config *config); + +/** +* @brief Opens a PCM by it's name. +* +* @param[in] The name of the PCM. +* @param[in] flags Specify characteristics and functionality about the pcm. +* @param[in] config The hardware and software parameters to open the PCM with. +* @returns On success, A PCM structure returned. On failure, NULL or invalid PCM structure returned. +* @since Tizen RT v1.1 +*/ +struct pcm *pcm_open_by_name(const char *name, + unsigned int flags, + const struct pcm_config *config); + +/** +* @brief Closes a PCM returned by @ref pcm_open. +* +* @param[in] A PCM returned by pcm_open. +* @return Always returns 0. +* @since Tizen RT v1.1 +*/ +int pcm_close(struct pcm *pcm); + +/** +* @brief Checks if a PCM file has been opened without error. +* +* @param[in] A PCM handle. +* @return On success, 0 returned. On failure, 1 returned +* @since Tizen RT v1.1 +*/ +int pcm_is_ready(const struct pcm *pcm); + +/** +* @brief Gets the channel count of the PCM. +* +* @param[in] A PCM handle. +* @return The channel count of the PCM. +* @since Tizen RT v1.1 +*/ +unsigned int pcm_get_channels(const struct pcm *pcm); + +/** +* @brief Gets the PCM configuration. +* +* @param[in] A PCM handle. +* @return On success, The PCM configuration returned. On failure, NULL returned. +* @since Tizen RT v1.1 +*/ +const struct pcm_config * pcm_get_config(const struct pcm *pcm); + +/** +* @brief Gets the sample rate of the PCM. +* +* @param[in] A PCM handle. +* @return The rate of the PCM. +* @since Tizen RT v1.1 +*/ +unsigned int pcm_get_rate(const struct pcm *pcm); + +/** +* @brief Gets the format of the PCM. +* +* @param[in] A PCM handle. +* @return The format of the PCM. +* @since Tizen RT v1.1 +*/ +enum pcm_format pcm_get_format(const struct pcm *pcm); + +/** +* @brief Gets the file descriptor of the PCM. +* +* @param[in] A PCM handle. +* @return The file descriptor of the PCM. +* @since Tizen RT v1.1 +*/ +int pcm_get_file_descriptor(const struct pcm *pcm); + +/** +* @brief Gets the error message for the last error that occured. +* +* @param[in] A PCM handle. +* @return The error message of the last error that occured. +* @since Tizen RT v1.1 +*/ +const char *pcm_get_error(const struct pcm *pcm); + +/** +* @brief Sets the PCM configuration parameters +* +* @param[out] A PCM handle. +* @param[in] The configuration to use for the PCM +* @returns On success, 0 returned. On failure, a negative errno value returned +* @since Tizen RT v1.1 +*/ +int pcm_set_config(struct pcm *pcm, const struct pcm_config *config); + +/** +* @brief Gets the buffer size of the PCM. +* +* @param[in] A PCM handle. +* @return The buffer size of the PCM. +* @since Tizen RT v1.1 +*/ +unsigned int pcm_get_buffer_size(const struct pcm *pcm); + +/** +* @brief Determines how many bytes are occupied by a number of frames of a PCM. +* +* @param[in] A PCM handle. +* @param[i] The number of frames of a PCM. +* @return The bytes occupied by frames. +* @since Tizen RT v1.1 +*/ +unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames); + +/** +* @brief Determines how many frames of a PCM can fit into a number of bytes. +* +* @param[in] A PCM handle. +* @param[in] The number of bytes. +* @return The number of frames that may fit into @p bytes +* @since Tizen RT v1.1 +*/ +unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes); + +/** +* @brief Gets the subdevice on which the pcm has been opened. +* +* @param[in] A PCM handle. +* @return The subdevice on which the pcm has been opened. +* @since Tizen RT v1.1 +*/ +unsigned int pcm_get_subdevice(const struct pcm *pcm); + +/** +* @brief Writes audio samples to PCM. +* +* @param[out] A PCM handle. +* @param[in] The audio sample array +* @param[in] The number of frames occupied by the sample array. +* @return On success, written number of frames returned. On failure, a negative number returned. +* @since Tizen RT v1.1 +*/ +int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count); + +/** +* @brief Reads audio samples from PCM. +* +* @param[out] A PCM handle. +* @param[out] The audio sample array which will contain the audio data recieved from the input stream +* @param[in] The number of frames that the user wants to read +* @return On success, number of frames read returned. On failure, a negative number returned. +* @since Tizen RT v1.1 +*/ +int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count); + +/** +* @brief Prepares a PCM, if it has not been prepared already. +* +* @param[in] A PCM handle. +* @return On success, 0 returned. On failure, a negative number returned. +* @since Tizen RT v1.1 +*/ +int pcm_prepare(struct pcm *pcm); + +/** +* @brief Starts a PCM. +* +* @param[out] A PCM handle. +* @return On success, 0 returned. On failure, a negative number returned. +* @since Tizen RT v1.1 +*/ +int pcm_start(struct pcm *pcm); + +/** +* @brief Stops a PCM. +* +* @param[out] A PCM handle. +* @return On success, 0 returned. On failure, a negative number returned. +* @since Tizen RT v1.1 +*/ +int pcm_stop(struct pcm *pcm); + +/** +* @brief Determines the number of bits occupied by a @ref pcm_format. +* +* @param[in] format A PCM format +* @return The number of bits associated with @p format +* @since Tizen RT v1.1 +*/ +unsigned int pcm_format_to_bits(enum pcm_format format); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +/** @} */ // end of TinyAlsa group + +#endif + diff --git a/framework/src/tinyalsa/Make.defs b/framework/src/tinyalsa/Make.defs new file mode 100644 index 0000000..33f5109 --- /dev/null +++ b/framework/src/tinyalsa/Make.defs @@ -0,0 +1,23 @@ +########################################################################### +# +# Copyright 2016 Samsung Electronics All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, + # software distributed under the License is distributed on an + # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + # either express or implied. See the License for the specific + # language governing permissions and limitations under the License. + # + ########################################################################### +CSRCS += tinyalsa.c + + +DEPPATH += --dep-path src/tinyalsa +VPATH += :src/tinyalsa + diff --git a/framework/src/tinyalsa/tinyalsa.c b/framework/src/tinyalsa/tinyalsa.c new file mode 100644 index 0000000..63efcdf --- /dev/null +++ b/framework/src/tinyalsa/tinyalsa.c @@ -0,0 +1,1241 @@ +/**************************************************************************** + * + * Copyright 2017 Samsung Electronics All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* tinyalsa.c +** +** Copyright 2011, The Android Open Source Project +** +** 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. +** * Neither the name of The Android Open Source Project nor the names of +** its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``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 Android Open Source Project 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. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define __force +#define __bitwise +#define __user + +#include +#include + +#define MILLI_TO_NAMO 1000000 + +#ifndef CONFIG_AUDIO_NUM_BUFFERS +#define CONFIG_AUDIO_NUM_BUFFERS 2 +#endif + +#ifndef CONFIG_AUDIO_BUFFER_NUMBYTES +#define CONFIG_AUDIO_BUFFER_NUMBYTES 8192 +#endif + +#define PCM_ERROR_MAX 128 + +/** A PCM handle. + * @ingroup libtinyalsa-pcm + */ +struct pcm { + /** The PCM's file descriptor */ + int fd; + /** Flags that were passed to @ref pcm_open */ + unsigned int flags; + /** Whether the PCM is running or not */ + int running:1; + /** Whether or not the PCM has been prepared */ + int prepared:1; + /** The number of underruns that have occured */ + int underruns; + /** Size of the buffer */ + unsigned int buffer_size; + /** The boundary for ring buffer pointers */ + unsigned int boundary; + /** Description of the last error that occured */ + char error[PCM_ERROR_MAX]; + /** Configuration that was passed to @ref pcm_open */ + struct pcm_config config; + unsigned int noirq_frames_per_msec; + /** The delay of the PCM, in terms of frames */ + long pcm_delay; + /** The subdevice corresponding to the PCM */ + unsigned int subdevice; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + void *session; +#endif + mqd_t mq; /* Message queue for the playthread */ + char mqname[16]; /* Name of our message queue */ +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + struct ap_buffer_info_s buf_info; + struct ap_buffer_s **pBuffers; +#else + struct ap_buffer_s *pBuffers[CONFIG_AUDIO_NUM_BUFFERS]; +#endif + unsigned int bufPtr; + struct ap_buffer_s *nextBuf; + unsigned int nextSize; + unsigned int nextOffset; +}; + +static int oops(struct pcm *pcm, int e, const char *fmt, ...) +{ + va_list ap; + int sz; + + va_start(ap, fmt); + vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); + va_end(ap); + sz = strlen(pcm->error); + + if (errno) { + snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, ": %s", strerror(e)); + } + return -1; +} + +/** Gets the buffer size of the PCM. + * @param pcm A PCM handle. + * @return The buffer size of the PCM. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_get_buffer_size(const struct pcm *pcm) +{ + return pcm->buffer_size; +} + +/** Gets the channel count of the PCM. + * @param pcm A PCM handle. + * @return The channel count of the PCM. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_get_channels(const struct pcm *pcm) +{ + return pcm->config.channels; +} + +/** Gets the PCM configuration. + * @param pcm A PCM handle. + * @return The PCM configuration. + * This function only returns NULL if + * @p pcm is NULL. + * @ingroup libtinyalsa-pcm + * */ +const struct pcm_config *pcm_get_config(const struct pcm *pcm) +{ + if (pcm == NULL) { + return NULL; + } + return &pcm->config; +} + +/** Gets the rate of the PCM. + * The rate is given in frames per second. + * @param pcm A PCM handle. + * @return The rate of the PCM. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_get_rate(const struct pcm *pcm) +{ + return pcm->config.rate; +} + +/** Gets the format of the PCM. + * @param pcm A PCM handle. + * @return The format of the PCM. + * @ingroup libtinyalsa-pcm + */ +enum pcm_format pcm_get_format(const struct pcm *pcm) +{ + return pcm->config.format; +} + +/** Gets the file descriptor of the PCM. + * Useful for extending functionality of the PCM when needed. + * @param pcm A PCM handle. + * @return The file descriptor of the PCM. + * @ingroup libtinyalsa-pcm + */ +int pcm_get_file_descriptor(const struct pcm *pcm) +{ + return pcm->fd; +} + +/** Gets the error message for the last error that occured. + * If no error occured and this function is called, the results are undefined. + * @param pcm A PCM handle. + * @return The error message of the last error that occured. + * @ingroup libtinyalsa-pcm + */ +const char *pcm_get_error(const struct pcm *pcm) +{ + return pcm->error; +} + +/** Sets the PCM configuration. + * @param pcm A PCM handle. + * @param config The configuration to use for the + * PCM. This parameter may be NULL, in which case + * the default configuration is used. + * @returns Zero on success, a negative errno value + * on failure. + * @ingroup libtinyalsa-pcm + * */ +int pcm_set_config(struct pcm *pcm, const struct pcm_config *config) +{ + struct audio_caps_desc_s cap_desc; + int ret; + + if (pcm == NULL) { + return -EFAULT; + } else if (config == NULL) { + config = &pcm->config; + pcm->config.channels = 2; + pcm->config.rate = 48000; + pcm->config.format = PCM_FORMAT_S16_LE; + pcm->config.start_threshold = 0; + pcm->config.stop_threshold = 0; + pcm->config.silence_threshold = 0; + } else { + pcm->config = *config; + } + +#ifdef CONFIG_AUDIO_MULTI_SESSION + cap_desc.session = pcm->session; +#endif + cap_desc.caps.ac_len = sizeof(struct audio_caps_s); + cap_desc.caps.ac_type = AUDIO_TYPE_OUTPUT; + cap_desc.caps.ac_channels = config->channels; + cap_desc.caps.ac_controls.hw[0] = config->rate; + cap_desc.caps.ac_controls.b[2] = pcm_format_to_bits(config->format); + + ret = ioctl(pcm->fd, AUDIOIOC_CONFIGURE, (unsigned long)&cap_desc); + if (ret < 0) { + return oops(pcm, -errno, "AUDIOIOC_CONFIGURE ioctl failed"); + } + + return 0; +} + +/** Gets the subdevice on which the pcm has been opened. + * @param pcm A PCM handle. + * @return The subdevice on which the pcm has been opened */ +unsigned int pcm_get_subdevice(const struct pcm *pcm) +{ + return pcm->subdevice; +} + +/** Determines the number of bits occupied by a @ref pcm_format. + * @param format A PCM format. + * @return The number of bits associated with @p format + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_format_to_bits(enum pcm_format format) +{ + switch (format) { + case PCM_FORMAT_S32_LE: + case PCM_FORMAT_S32_BE: + case PCM_FORMAT_S24_LE: + case PCM_FORMAT_S24_BE: + return 32; + case PCM_FORMAT_S24_3LE: + case PCM_FORMAT_S24_3BE: + return 24; + default: + case PCM_FORMAT_S16_LE: + case PCM_FORMAT_S16_BE: + return 16; + case PCM_FORMAT_S8: + return 8; + }; +} + +/** Determines how many frames of a PCM can fit into a number of bytes. + * @param pcm A PCM handle. + * @param bytes The number of bytes. + * @return The number of frames that may fit into @p bytes + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes) +{ + return bytes / (pcm->config.channels * (pcm_format_to_bits(pcm->config.format) >> 3)); +} + +/** Determines how many bytes are occupied by a number of frames of a PCM. + * @param pcm A PCM handle. + * @param frames The number of frames of a PCM. + * @return The bytes occupied by @p frames. + * @ingroup libtinyalsa-pcm + */ +unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames) +{ + return frames * pcm->config.channels * (pcm_format_to_bits(pcm->config.format) >> 3); +} + +static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset, char *buf, unsigned int src_offset, unsigned int frames) +{ + int size_bytes = pcm_frames_to_bytes(pcm, frames); + int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset); + int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset); + + /* interleaved only atm */ + if (pcm->flags & PCM_IN) { + memcpy(buf + src_offset_bytes, (char *)pcm->nextBuf + pcm_offset_bytes, size_bytes); + } else { + memcpy((char *)pcm->nextBuf + pcm_offset_bytes, buf + src_offset_bytes, size_bytes); + } + return 0; +} + +static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf, unsigned int offset, unsigned int size) +{ + void *pcm_areas; + int commit; + unsigned int pcm_offset, frames, count = 0; + + while (size > 0) { + frames = size; + pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); + pcm_areas_copy(pcm, pcm_offset, buf, offset, frames); + commit = pcm_mmap_commit(pcm, pcm_offset, frames); + if (commit < 0) { + return oops(pcm, commit, "failed to commit %d frames\n", frames); + } + + offset += commit; + count += commit; + size -= commit; + } + return count; +} + +/** Returns available frames in pcm buffer and corresponding time stamp. + * The clock is CLOCK_MONOTONIC if flag @ref PCM_MONOTONIC was specified in @ref pcm_open, + * otherwise the clock is CLOCK_REALTIME. + * For an input stream, frames available are frames ready for the application to read. + * For an output stream, frames available are the number of empty frames available for the application to write. + * Only available for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param avail The number of available frames + * @param tstamp The timestamp + * @return On success, zero is returned; on failure, negative one. + */ +int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *tstamp) +{ + return -1; +} + +/** Writes audio samples to PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_OUT flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param frame_count The number of frames occupied by the sample array. + * This value should not be greater than @ref TINYALSA_FRAMES_MAX + * or INT_MAX. + * @return On success, this function returns the number of frames written; otherwise, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count) +{ + int nbytes; + struct audio_buf_desc_s bufdesc; + struct ap_buffer_s *apb; + struct audio_msg_s msg; + unsigned int size; + int prio; + + if (pcm->flags & PCM_IN) { + return -EINVAL; + } + + nbytes = pcm_frames_to_bytes(pcm, frame_count); + +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + if (nbytes > pcm->config.period_size) { + nbytes = pcm->config.period_size; + } + + if (pcm->bufPtr < pcm->config.period_count) +#else + if (nbytes > CONFIG_AUDIO_BUFFER_NUMBYTES) { + nbytes = CONFIG_AUDIO_BUFFER_NUMBYTES; + } + + if (pcm->bufPtr < CONFIG_AUDIO_NUM_BUFFERS) +#endif + { + /* If we have empty buffers, fill them first */ + memcpy(pcm->pBuffers[pcm->bufPtr]->samp, data, nbytes); + apb = pcm->pBuffers[pcm->bufPtr]; + pcm->bufPtr++; + } else { + /* We dont have any empty buffers. wait for deque message from kernel */ + size = mq_receive(pcm->mq, (FAR char *)&msg, sizeof(msg), &prio); + if (size != sizeof(msg)) { + /* Interrupted by a signal? What to do? */ + return oops(pcm, EINTR, "Interrupted while waiting for deque message from kernel"); + } + if (msg.msgId == AUDIO_MSG_DEQUEUE) { + apb = (struct ap_buffer_s *)msg.u.pPtr; + memcpy(apb->samp, data, nbytes); + } else { + return oops(pcm, EINTR, "Recieved unexpected msg (id = %d) while waiting for deque message from kernel", msg.msgId); + } + } + + /* Buffer is ready. Enque it */ +#ifdef CONFIG_AUDIO_MULTI_SESSION + bufdesc.session = pcm->session; +#endif + bufdesc.numbytes = apb->nbytes; + bufdesc.u.pBuffer = apb; + if (ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, (unsigned long)&bufdesc) < 0) { + return oops(pcm, errno, "AUDIOIOC_ENQUEUEBUFFER ioctl failed"); + } + /* If playback is not already started, start now! */ + if ((!pcm->running) && (pcm_start(pcm) < 0)) { + return -errno; + } else { + pcm->running = 1; + } + + return pcm_bytes_to_frames(pcm, nbytes); +} + +/** Reads audio samples from PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_IN flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param frame_count The number of frames occupied by the sample array. + * This value should not be greater than @ref TINYALSA_FRAMES_MAX + * or INT_MAX. + * @return On success, this function returns the number of frames written; otherwise, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count) +{ + int nbytes; + struct audio_buf_desc_s bufdesc; + struct ap_buffer_s *apb; + struct audio_msg_s msg; + unsigned int size; + int prio; + + if (!(pcm->flags & PCM_IN)) { + return -EINVAL; + } + + nbytes = pcm_frames_to_bytes(pcm, frame_count); + +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + if (nbytes < pcm->config.period_size) { + return -EINVAL; + } + bufdesc.numbytes = pcm->config.period_size; +#else + if (nbytes < CONFIG_AUDIO_BUFFER_NUMBYTES) { + return -EINVAL; + } + bufdesc.numbytes = CONFIG_AUDIO_BUFFER_NUMBYTES; +#endif +#ifdef CONFIG_AUDIO_MULTI_SESSION + bufdesc.session = pcm->session; +#endif + + /* If device is not yet started, start now! */ + if ((!pcm->running) && (pcm_start(pcm) < 0)) { + return -errno; + } else { + pcm->running = 1; + } + + /* Wait for deque message from kernel */ + size = mq_receive(pcm->mq, (FAR char *)&msg, sizeof(msg), &prio); + if (size != sizeof(msg)) { + /* Interrupted by a signal? What to do? */ + return oops(pcm, EINTR, "Interrupted while waiting for deque message from kernel"); + } + if (msg.msgId == AUDIO_MSG_DEQUEUE) { + apb = (struct ap_buffer_s *)msg.u.pPtr; + /* Copy data to user buffer */ + memcpy(data, apb->samp, apb->nbytes); + /* Enque buffer for next read opertion */ + bufdesc.u.pBuffer = apb; + if (ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, (unsigned long)&bufdesc) < 0) { + return oops(pcm, errno, "failed to enque buffer after read"); + } + } else { + return oops(pcm, EINTR, "Recieved unexpected msg (id = %d) while waiting for deque message from kernel", msg.msgId); + } + + return pcm_bytes_to_frames(pcm, apb->nbytes); +} + +/** Writes audio samples to PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_OUT flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param count The number of bytes occupied by the sample array. + * @return On success, this function returns zero; otherwise, a negative number. + * @deprecated + * @ingroup libtinyalsa-pcm + */ +int pcm_write(struct pcm *pcm, const void *data, unsigned int count) +{ + return pcm_writei(pcm, data, pcm_bytes_to_frames(pcm, count)) > 0 ? 0 : -1; +} + +/** Reads audio samples from PCM. + * If the PCM has not been started, it is started in this function. + * This function is only valid for PCMs opened with the @ref PCM_IN flag. + * This function is not valid for PCMs opened with the @ref PCM_MMAP flag. + * @param pcm A PCM handle. + * @param data The audio sample array + * @param count The number of bytes occupied by the sample array. + * @return On success, this function returns zero; otherwise, a negative number. + * @deprecated + * @ingroup libtinyalsa-pcm + */ +int pcm_read(struct pcm *pcm, void *data, unsigned int count) +{ + return pcm_readi(pcm, data, pcm_bytes_to_frames(pcm, count)) > 0 ? 0 : -1; +} + +static struct pcm bad_pcm = { + .fd = -1, +}; + +/** Gets the hardware parameters of a PCM, without created a PCM handle. + * @param card The card of the PCM. + * The default card is zero. + * @param device The device of the PCM. + * The default device is zero. + * @param flags Specifies whether the PCM is an input or output. + * May be one of the following: + * - @ref PCM_IN + * - @ref PCM_OUT + * @return On success, the hardware parameters of the PCM; on failure, NULL. + * @ingroup libtinyalsa-pcm + */ + +#if 0 +struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, unsigned int flags) +{ + return NULL; +} + +/** Frees the hardware parameters returned by @ref pcm_params_get. + * @param pcm_params Hardware parameters of a PCM. + * May be NULL. + * @ingroup libtinyalsa-pcm + */ +void pcm_params_free(struct pcm_params *pcm_params) +{ +} + +static int pcm_param_to_alsa(enum pcm_param param) +{ + return -1; +} + +/** Gets a mask from a PCM's hardware parameters. + * @param pcm_params A PCM's hardware parameters. + * @param param The parameter to get. + * @return If @p pcm_params is NULL or @p param is not a mask, NULL is returned. + * Otherwise, the mask associated with @p param is returned. + * @ingroup libtinyalsa-pcm + */ +const struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params, enum pcm_param param) +{ + return NULL; +} + +/** Get the minimum of a specified PCM parameter. + * @param pcm_params A PCM parameters structure. + * @param param The specified parameter to get the minimum of. + * @returns On success, the parameter minimum. + * On failure, zero. + */ +unsigned int pcm_params_get_min(const struct pcm_params *pcm_params, enum pcm_param param) +{ + return 0; +} + +/** Get the maximum of a specified PCM parameter. + * @param pcm_params A PCM parameters structure. + * @param param The specified parameter to get the maximum of. + * @returns On success, the parameter maximum. + * On failure, zero. + */ +unsigned int pcm_params_get_max(const struct pcm_params *pcm_params, enum pcm_param param) +{ + return 0; +} +#endif + +/** Closes a PCM returned by @ref pcm_open. + * @param pcm A PCM returned by @ref pcm_open. + * May not be NULL. + * @return Always returns zero. + * @ingroup libtinyalsa-pcm + */ +int pcm_close(struct pcm *pcm) +{ + int x; + struct audio_buf_desc_s buf_desc; + + if (pcm == NULL) { + return oops(pcm, EINVAL, "pcm is null"); + } + + if (pcm == &bad_pcm) { + return 0; + } + + if (pcm->running) { + pcm_stop(pcm); + } + + ioctl(pcm->fd, AUDIOIOC_UNREGISTERMQ, (unsigned long)pcm->mq); +#ifdef CONFIG_AUDIO_MULTI_SESSION + ioctl(pcm->fd, AUDIOIOC_RELEASE, (unsigned long)pcm->session); +#else + ioctl(pcm->fd, AUDIOIOC_RELEASE, 0); +#endif + +#ifdef CONFIG_AUDIO_MULTI_SESSION + buf_desc.session = pcm->session; +#endif +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + if (pBuffers != NULL) { + for (x = 0; x < pcm->config.period_count; x++) { + if (pcm->pBuffers[x] != NULL) { + buf_desc.u.pBuffer = pcm->pBuffers[x]; + ioctl(pPlayer->devFd, AUDIOIOC_FREEBUFFER, (unsigned long)&buf_desc); + } + } + free(pBuffers); + } +#else + for (x = 0; x < CONFIG_AUDIO_NUM_BUFFERS; x++) { + if (pcm->pBuffers[x] != NULL) { + buf_desc.u.pBuffer = pcm->pBuffers[x]; + ioctl(pcm->fd, AUDIOIOC_FREEBUFFER, (unsigned long)&buf_desc); + } + } +#endif + mq_close(pcm->mq); /* Close the message queue */ + mq_unlink(pcm->mqname); + + if (pcm->fd >= 0) { + close(pcm->fd); + } + pcm->prepared = 0; + pcm->running = 0; + pcm->buffer_size = 0; + pcm->fd = -1; + free(pcm); + return 0; +} + +/** Opens a PCM by it's name. + * @param name The name of the PCM. + * The name is given in the format: hw:card,device + * @param flags Specify characteristics and functionality about the pcm. + * May be a bitwise AND of the following: + * - @ref PCM_IN + * - @ref PCM_OUT + * - @ref PCM_MMAP + * - @ref PCM_NOIRQ + * - @ref PCM_MONOTONIC + * @param config The hardware and software parameters to open the PCM with. + * @returns A PCM structure. + * If an error occurs allocating memory for the PCM, NULL is returned. + * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready. + * If @ref pcm_is_ready, check @ref pcm_get_error for more information. + * @ingroup libtinyalsa-pcm + */ +struct pcm *pcm_open_by_name(const char *name, unsigned int flags, const struct pcm_config *config) +{ + unsigned int card, device; + if ((name[0] != 'h') + || (name[1] != 'w') + || (name[2] != ':')) { + return NULL; + } else if (sscanf(&name[3], "%u,%u", &card, &device) != 2) { + return NULL; + } + return pcm_open(card, device, flags, config); +} + +/** Opens a PCM. + * @param card The card that the pcm belongs to. + * The default card is zero. + * @param device The device that the pcm belongs to. + * The default device is zero. + * @param flags Specify characteristics and functionality about the pcm. + * May be a bitwise AND of the following: + * - @ref PCM_IN + * - @ref PCM_OUT + * - @ref PCM_MMAP + * - @ref PCM_NOIRQ + * - @ref PCM_MONOTONIC + * @param config The hardware and software parameters to open the PCM with. + * @returns A PCM structure. + * If an error occurs allocating memory for the PCM, NULL is returned. + * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready. + * If @ref pcm_is_ready, check @ref pcm_get_error for more information. + * @ingroup libtinyalsa-pcm + */ +struct pcm *pcm_open(unsigned int card, unsigned int device, unsigned int flags, const struct pcm_config *config) +{ + struct pcm *pcm; + char fn[256]; + int ret = 0; + struct mq_attr attr; + struct audio_buf_desc_s buf_desc; + int x; + + pcm = malloc(sizeof(struct pcm)); + memset(pcm, 0, sizeof(struct pcm)); + + if (!pcm) { + return &bad_pcm; + } + + snprintf(fn, sizeof(fn), "/dev/audio/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p'); + + pcm->flags = flags; + pcm->fd = open(fn, O_RDWR); + if (pcm->fd < 0) { + oops(pcm, errno, "cannot open device '%s'", fn); + return pcm; + } + + /* Try to reserve the device */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + ret = ioctl(pcm->fd, AUDIOIOC_RESERVE, (unsigned long)&pcm->session); +#else + ret = ioctl(pcm->fd, AUDIOIOC_RESERVE, 0); +#endif + if (ret < 0) { + /* Device is busy or error */ + oops(pcm, errno, "Failed to reserve device"); + ret = -errno; + goto fail_close; + } + + if (pcm_set_config(pcm, config) != 0) { + goto fail_close; + } + + /* Create a message queue for the playthread */ + attr.mq_maxmsg = 16; + attr.mq_msgsize = sizeof(struct audio_msg_s); + attr.mq_curmsgs = 0; + attr.mq_flags = 0; + + snprintf(pcm->mqname, sizeof(pcm->mqname), "/tmp/%0lx", (unsigned long)((uintptr_t) pcm)); + + pcm->mq = mq_open(pcm->mqname, O_RDWR | O_CREAT, 0644, &attr); + if (pcm->mq == NULL) { + /* Unable to open message queue! */ + ret = -errno; + oops(pcm, errno, "mq_open failed"); + goto fail_close; + } + + /* Register our message queue with the audio device */ + ioctl(pcm->fd, AUDIOIOC_REGISTERMQ, (unsigned long)pcm->mq); + +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + if ((ret = ioctl(pcm->fd, AUDIOIOC_GETBUFFERINFO, (unsigned long)&buf_info)) != OK) { + /* Driver doesn't report it's buffer size. Use our default. */ + buf_info.buffer_size = CONFIG_AUDIO_BUFFER_NUMBYTES; + buf_info.nbuffers = CONFIG_AUDIO_NUM_BUFFERS; + } + + pcm->config.period_size = buf_info.buffer_size; + pcm->config.period_count = buf_info.nbuffers; + pcm->buffer_size = pcm->config.period_size; + + /* Create array of pointers to buffers */ + pcm->pBuffers = (FAR struct ap_buffer_s **)malloc(buf_info.nbuffers * sizeof(FAR void *)); + if (pcm->pBuffers == NULL) { + /* Error allocating memory for buffer storage! */ + ret = -ENOMEM; + goto fail_after_mq; + } + + /* Create our audio pipeline buffers to use for queueing up data */ + + for (x = 0; x < buf_info.nbuffers; x++) { + pcm->pBuffers[x] = NULL; + } + + for (x = 0; x < buf_info.nbuffers; x++) +#else /* CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFER */ + + pcm->config.period_size = CONFIG_AUDIO_BUFFER_NUMBYTES; + pcm->config.period_count = CONFIG_AUDIO_NUM_BUFFERS; + pcm->buffer_size = pcm->config.period_size; + + for (x = 0; x < CONFIG_AUDIO_NUM_BUFFERS; x++) { + pcm->pBuffers[x] = NULL; + } + + for (x = 0; x < CONFIG_AUDIO_NUM_BUFFERS; x++) +#endif /* CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFER */ + { + /* Fill in the buffer descriptor struct to issue an alloc request */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + buf_desc.session = pcm->session; +#endif +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + buf_desc.numbytes = buf_info.buffer_size; +#else + buf_desc.numbytes = CONFIG_AUDIO_BUFFER_NUMBYTES; +#endif + buf_desc.u.ppBuffer = &pcm->pBuffers[x]; + + ret = ioctl(pcm->fd, AUDIOIOC_ALLOCBUFFER, (unsigned long)&buf_desc); + + if (ret != sizeof(buf_desc)) { + /* Buffer alloc Operation not supported or error allocating! */ + oops(pcm, -1, "Could not allocate buffer %d\n", x); + goto fail_cleanup_buffers; + } + } + + pcm->underruns = 0; + return pcm; + +fail_cleanup_buffers: +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + if (pcm->pBuffers != NULL) { + for (x = 0; x < buf_info.nbuffers; x++) { + /* Fill in the buffer descriptor struct to issue a free request */ + if (pcm->pBuffers[x] != NULL) { + buf_desc.u.pBuffer = pBuffers[x]; + ioctl(pPlayer->devFd, AUDIOIOC_FREEBUFFER, (unsigned long)&buf_desc); + } + } + /* Free the pointers to the buffers */ + free(pcm->pBuffers); + } +#else + for (x = 0; x < CONFIG_AUDIO_NUM_BUFFERS; x++) { + /* Fill in the buffer descriptor struct to issue a free request */ + if (pcm->pBuffers[x] != NULL) { + buf_desc.u.pBuffer = pcm->pBuffers[x]; + ioctl(pcm->fd, AUDIOIOC_FREEBUFFER, (unsigned long)&buf_desc); + } + } +#endif + + pcm->bufPtr = 0; + +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS +fail_after_mq: + mq_close(pcm->mq); /* Close the message queue */ + mq_unlink(pcm->mqname); /* Unlink the message queue */ +#endif +fail_close: + close(pcm->fd); + pcm->fd = -1; + return pcm; +} + +/** Checks if a PCM file has been opened without error. + * @param pcm A PCM handle. + * May be NULL. + * @return If a PCM's file descriptor is not valid or the pointer is NULL, it returns zero. + * Otherwise, the function returns one. + * @ingroup libtinyalsa-pcm + */ +int pcm_is_ready(const struct pcm *pcm) +{ + if (pcm != NULL) { + return pcm->fd >= 0; + } + return 0; +} + +/** Links two PCMs. + * After this function is called, the two PCMs will prepare, start and stop in sync (at the same time). + * If an error occurs, the error message will be written to @p pcm1. + * @param pcm1 A PCM handle. + * @param pcm2 Another PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_link(struct pcm *pcm1, struct pcm *pcm2) +{ + return -1; +} + +/** Unlinks a PCM. + * @see @ref pcm_link + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_unlink(struct pcm *pcm) +{ + return -1; +} + +/** Prepares a PCM, if it has not been prepared already. + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_prepare(struct pcm *pcm) +{ + if (pcm->prepared) { + return 0; + } + + pcm->prepared = 1; + return 0; +} + +/** Starts a PCM. + * If the PCM has not been prepared, + * it is prepared in this function. + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_start(struct pcm *pcm) +{ + struct audio_buf_desc_s bufdesc; + + int prepare_error = pcm_prepare(pcm); + if (prepare_error) { + return prepare_error; + } + + if (pcm->flags & PCM_IN) { + /* If the device is opened for read and Our buffers are not enqued. Enque them now. */ +#ifdef CONFIG_AUDIO_MULTI_SESSION + bufdesc.session = pcm->session; +#endif +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + bufdesc.numbytes = pcm->config.period_size; + for (pcm->bufPtr = 0; pcm->bufPtr < buf_info.nbuffers; pcm->bufPtr++) +#else + bufdesc.numbytes = CONFIG_AUDIO_BUFFER_NUMBYTES; + for (pcm->bufPtr = 0; pcm->bufPtr < CONFIG_AUDIO_NUM_BUFFERS; pcm->bufPtr++) +#endif + { + bufdesc.u.pBuffer = pcm->pBuffers[pcm->bufPtr]; + if (ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, (unsigned long)&bufdesc) < 0) { + return oops(pcm, errno, "AUDIOIOC_ENQUEUEBUFFER ioctl failed"); + } + } + } +#ifdef CONFIG_AUDIO_MULTI_SESSION + if (ioctl(pcm->fd, AUDIOIOC_START, (unsigned long)pcm->session) < 0) +#else + if (ioctl(pcm->fd, AUDIOIOC_START, 0) < 0) +#endif + { + return oops(pcm, errno, "cannot start channel"); + } + + pcm->nextSize = 0; + pcm->nextOffset = 0; + pcm->nextBuf = NULL; + pcm->running = 1; + return 0; +} + +/** Stops a PCM. + * @param pcm A PCM handle. + * @return On success, zero; on failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +int pcm_stop(struct pcm *pcm) +{ +#ifdef CONFIG_AUDIO_MULTI_SESSION + if (ioctl(pcm->fd, AUDIOIOC_STOP, (unsigned long)pcm->session) < 0) +#else + if (ioctl(pcm->fd, AUDIOIOC_STOP, 0) < 0) +#endif + return oops(pcm, errno, "cannot stop channel"); + + pcm->prepared = 0; + pcm->running = 0; + pcm->bufPtr = 0; + pcm->nextSize = 0; + pcm->nextOffset = 0; + pcm->nextBuf = NULL; + + return 0; +} + +static inline int pcm_mmap_avail(struct pcm *pcm) +{ + return pcm_bytes_to_frames(pcm, pcm->nextSize); +} + +int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, unsigned int *frames) +{ + int nframes = pcm_bytes_to_frames(pcm, pcm->nextSize); + + /* If data is not available, return -1 */ + if (nframes == 0) { + *frames = 0; + return -1; + } + + *areas = pcm->nextBuf->samp; + *offset = pcm->nextOffset; + + if (*frames < nframes) { + pcm->nextSize -= pcm_frames_to_bytes(pcm, *frames); + pcm->nextOffset += pcm_frames_to_bytes(pcm, *frames); + } else { + *frames = pcm_bytes_to_frames(pcm, pcm->nextSize); + pcm->nextSize = 0; + pcm->nextOffset = 0; + } + + return 0; +} + +int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames) +{ + /* not used */ + (void)offset; + + struct audio_buf_desc_s bufdesc; + + if (pcm->nextSize == 0) { +#ifdef CONFIG_AUDIO_MULTI_SESSION + bufdesc.session = pcm->session; +#endif + bufdesc.numbytes = pcm->nextBuf->nmaxbytes; + bufdesc.u.pBuffer = pcm->nextBuf; + if (ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, (unsigned long)&bufdesc) < 0) { + return oops(pcm, errno, "AUDIOIOC_ENQUEUEBUFFER ioctl failed"); + } + pcm->nextBuf = NULL; + } + + return frames; +} + +int pcm_avail_update(struct pcm *pcm) +{ + return pcm_mmap_avail(pcm); +} + +/** Waits for frames to be available for read or write operations. + * @param pcm A PCM handle. + * @param timeout The maximum amount of time to wait for, in terms of milliseconds. + * @returns If frames became available, one is returned. + * If a timeout occured, zero is returned. + * If an error occured, a negative number is returned. + * @ingroup libtinyalsa-pcm + */ +int pcm_wait(struct pcm *pcm, int timeout) +{ + struct ap_buffer_s *apb; + struct audio_msg_s msg; + unsigned int size; + int prio; + struct timespec st_time; + +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + if ((pcm->flags & PCM_OUT) && pcm->bufPtr < pcm->config.period_count) +#else + if ((pcm->flags & PCM_OUT) && pcm->bufPtr < CONFIG_AUDIO_NUM_BUFFERS) +#endif + { + /* In playback scenario, if audio buffers are available, return immediately */ + pcm->nextBuf = pcm->pBuffers[pcm->bufPtr]; + pcm->nextSize = pcm->pBuffers[pcm->bufPtr]->nmaxbytes; + pcm->bufPtr++; + return 1; + } else { + /* We dont have any empty buffers. wait for deque message from kernel */ + if (timeout > 0) { + clock_gettime(CLOCK_REALTIME, &st_time); + st_time.tv_nsec += timeout * MILLI_TO_NANO; + size = mq_timedreceive(pcm->mq, (FAR char *)&msg, sizeof(msg), &prio, &st_time); + } else { + size = mq_receive(pcm->mq, (FAR char *)&msg, sizeof(msg), &prio); + } + + if (size != sizeof(msg)) { + if (errno == ETIMEDOUT) { + oops(pcm, errno, "TIMEOUT while watiting for deque message from kernel"); + return 0; + } else { + return oops(pcm, errno, "Interrupted while waiting for deque message from kernel"); + } + } + if (msg.msgId == AUDIO_MSG_DEQUEUE) { + apb = (struct ap_buffer_s *)msg.u.pPtr; + pcm->nextBuf = apb; + pcm->nextSize = apb->nbytes; + return 1; + } else { + return oops(pcm, EINTR, "Recieved unexpected msg (id = %d) while waiting for deque message from kernel", msg.msgId); + } + } +} + +int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes) +{ + int err = 0, frames, avail; + unsigned int offset = 0, count; + + if (bytes == 0) { + return 0; + } + + count = pcm_bytes_to_frames(pcm, bytes); + + while (count > 0) { + + /* get the available space for writing new frames */ + avail = pcm_avail_update(pcm); + if (avail < 0) { + return oops(pcm, 1, "cannot determine available mmap frames"); + } + + /* start the audio if we reach the threshold */ + if (!pcm->running) { + if (pcm_start(pcm) < 0) { + return oops(pcm, errno, "start error: avail 0x%x\n", avail); + } + } + + /* sleep until we have space to write new frames */ + if (pcm->running && (unsigned int)avail == 0) { + int time = -1; + + /* We will not support NOIRQ flag presently */ +#if 0 + if (pcm->flags & PCM_NOIRQ) + time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min) + / pcm->noirq_frames_per_msec; +#endif + err = pcm_wait(pcm, time); + if (err < 0) { + pcm->prepared = 0; + pcm->running = 0; + return oops(pcm, errno, "wait error: avail 0x%x\n", avail); + } + continue; + } + + frames = count; + if (frames > avail) { + frames = avail; + } + + if (!frames) { + break; + } + + /* copy frames from buffer */ + frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames); + if (frames < 0) { + return oops(pcm, 1, "write error: avail 0x%x\n", avail); + } + + offset += frames; + count -= frames; + } + + return 0; +} + +int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count) +{ + if ((~pcm->flags) & (PCM_OUT | PCM_MMAP)) { + return -ENOSYS; + } + + return pcm_mmap_transfer(pcm, (void *)data, count); +} + +int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count) +{ + if ((~pcm->flags) & (PCM_IN | PCM_MMAP)) { + return -ENOSYS; + } + + return pcm_mmap_transfer(pcm, data, count); +} + +#if 0 +/** Gets the delay of the PCM, in terms of frames. + * @param pcm A PCM handle. + * @returns On success, the delay of the PCM. + * On failure, a negative number. + * @ingroup libtinyalsa-pcm + */ +long pcm_get_delay(struct pcm *pcm) +{ + return -1; +} +#endif