diff --git a/audioutils/alsa-lib/CMakeLists.txt b/audioutils/alsa-lib/CMakeLists.txt new file mode 100644 index 00000000000..babb39fdaa1 --- /dev/null +++ b/audioutils/alsa-lib/CMakeLists.txt @@ -0,0 +1,36 @@ +# ############################################################################## +# apps/audioutils/alsa-lib/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you 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. +# +# ############################################################################## + +if(CONFIG_AUDIOUTILS_ALSA_LIB) + target_sources( + apps + PRIVATE bits_covert.c + channels_map.c + error.c + pcm_dmix_generic.c + pcm_dmix.c + pcm_hw.c + pcm_params.c + pcm_softvol.c + pcm.c) + + add_definitions(-DOUTSIDE_SPEEX) + add_definitions(-DRANDOM_PREFIX=nuttx) +endif() diff --git a/audioutils/alsa-lib/Kconfig b/audioutils/alsa-lib/Kconfig new file mode 100644 index 00000000000..f40319d123d --- /dev/null +++ b/audioutils/alsa-lib/Kconfig @@ -0,0 +1,66 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config AUDIOUTILS_ALSA_LIB + bool "Enable ALSA lib" + default n + +if AUDIOUTILS_ALSA_LIB + +config AUDIOUTILS_ALSA_LIB_DEV_PATH + string "play/recored device path" + default "/dev/audio" + +config AUDIOUTILS_ALSA_LIB_DUMP_FLAG + string "play pcm dump flag" + default "/data/alsa_pcm_dump" + +config AUDIOUTILS_ALSA_LIB_DUMP_PATH + string "play pcm dump path" + default "/data" + +choice + prompt "Alsa Device to be used" + default AUDIOUTILS_ALSA_LIB_DEVICE_DMIX + +config AUDIOUTILS_ALSA_LIB_DEVICE_DMIX + bool "Use Dmix device(support mix)" + +config AUDIOUTILS_ALSA_LIB_DEVICE_HW + bool "Use hardware device(not support mix, more faster)" + +endchoice # Alsa lib Device + +config AUDIOUTILS_ALSA_LIB_OUTPUT_FORMAT + int "Dmix output format 0/16/32 (16: S16, 32: S32, 0: use input format)" + default 0 + +config AUDIOUTILS_ALSA_LIB_OUTPUT_CHANNELS + int "Dmix output channels 0/1/2 (1: 1ch, 2: 2ch, 0: use input channels)" + default 0 + +config AUDIOUTILS_ALSA_LIB_OUTPUT_RATE + int "Dmix output rate 0/8000/16000/32000/44100/48000/96000 (0: use input rate)" + default 0 + +choice + prompt "Alsa LOG Output Controls" + default AUDIOUTILS_ALSA_LIB_LOG_INFO + +config AUDIOUTILS_ALSA_LIB_LOG_DEBUG + bool "Output DEBUG+ log" + +config AUDIOUTILS_ALSA_LIB_LOG_INFO + bool "Output INFO+ log" + +config AUDIOUTILS_ALSA_LIB_LOG_WARN + bool "Output WARN+ log" + +config AUDIOUTILS_ALSA_LIB_LOG_ERR + bool "Output ERROR+ log" + +endchoice # Alsa LOG Output Controls + +endif # AUDIOUTILS_ALSA_LIB diff --git a/audioutils/alsa-lib/Make.defs b/audioutils/alsa-lib/Make.defs new file mode 100644 index 00000000000..97fec904c0a --- /dev/null +++ b/audioutils/alsa-lib/Make.defs @@ -0,0 +1,28 @@ +############################################################################ +# apps/audioutils/alsa-lib/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +ifneq ($(CONFIG_AUDIOUTILS_ALSA_LIB),) + +CONFIGURED_APPS += $(APPDIR)/audioutils/alsa-lib + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/audioutils/alsa-lib/include +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/audioutils/alsa-lib/include + +endif diff --git a/audioutils/alsa-lib/Makefile b/audioutils/alsa-lib/Makefile new file mode 100644 index 00000000000..3813dc735ec --- /dev/null +++ b/audioutils/alsa-lib/Makefile @@ -0,0 +1,27 @@ +############################################################################ +# apps/audioutils/alsa-lib/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +include $(APPDIR)/Make.defs + +CSRCS += $(wildcard *.c) + +CFLAGS += -DOUTSIDE_SPEEX -DRANDOM_PREFIX=nuttx + +include $(APPDIR)/Application.mk diff --git a/audioutils/alsa-lib/bits_convert.c b/audioutils/alsa-lib/bits_convert.c new file mode 100644 index 00000000000..ec5ecdef113 --- /dev/null +++ b/audioutils/alsa-lib/bits_convert.c @@ -0,0 +1,172 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/bits_convert.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "bits_convert.h" +#include "pcm_local.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bitsconv_init(FAR struct bitsconv_data **bc, int channels, + snd_pcm_format_t src_format, snd_pcm_format_t dst_format, + int buf_size) +{ + if (*bc != NULL) + { + return 0; + } + + /* support s16->s32, s32->s16 only */ + + if ((src_format != SND_PCM_FORMAT_S16_LE && + src_format != SND_PCM_FORMAT_S32_LE) || + (dst_format != SND_PCM_FORMAT_S16_LE && + dst_format != SND_PCM_FORMAT_S32_LE)) + { + SND_ERR("format not supported! src: %d, dst: %d", src_format, + dst_format); + return -ENOTSUP; + } + + *bc = malloc(sizeof(struct bitsconv_data)); + if (*bc == NULL) + { + SND_ERR("bitsconv_init failed"); + return -ENOMEM; + } + + (*bc)->bitsconv_buf = malloc(buf_size); + if ((*bc)->bitsconv_buf == NULL) + { + free(*bc); + *bc = NULL; + SND_ERR("malloc bitsconv_buf failed"); + return -ENOMEM; + } + + (*bc)->channels = channels; + (*bc)->src_format = src_format; + (*bc)->dst_format = dst_format; + (*bc)->buf_size = buf_size; + + return 0; +} + +int bitsconv_process(FAR struct bitsconv_data *bc, FAR const void *in_data, + int in_size, FAR void **out_data, FAR int *out_size) +{ + int out_len; + int frames; + int ch; + + if (!bc) + { + SND_ERR("bitsconv_init not run"); + return -EPERM; + } + + if (bc->src_format == SND_PCM_FORMAT_S16_LE && + bc->dst_format == SND_PCM_FORMAT_S32_LE) + { + const int16_t *src; + int32_t *dst; + + out_len = in_size * bc->channels * sizeof(int32_t); + if (out_len > bc->buf_size) + { + SND_ERR("bitsconv out_buf too small"); + return -EPERM; + } + + for (ch = 0; ch < bc->channels; ch++) + { + frames = in_size; + src = (const int16_t *)in_data; + dst = (int32_t *)bc->bitsconv_buf; + while (frames-- > 0) + { + *(dst + ch) = (int32_t)((*(src + ch)) << 16); + src += bc->channels; + dst += bc->channels; + } + } + } + else if (bc->src_format == SND_PCM_FORMAT_S32_LE && + bc->dst_format == SND_PCM_FORMAT_S16_LE) + { + int32_t *src; + int16_t *dst; + + out_len = in_size * bc->channels * sizeof(int16_t); + if (out_len > bc->buf_size) + { + SND_ERR("bitsconv out_buf too small"); + return -EPERM; + } + + for (ch = 0; ch < bc->channels; ch++) + { + frames = in_size; + src = (int32_t *)in_data; + dst = (int16_t *)bc->bitsconv_buf; + while (frames-- > 0) + { + *(dst + ch) = (int16_t)((*(src + ch)) >> 16); + src += bc->channels; + dst += bc->channels; + } + } + } + else + { + SND_ERR("unknown format"); + return -ENOTSUP; + } + + *out_data = bc->bitsconv_buf; + *out_size = in_size; + + return 0; +} + +int bitsconv_release(FAR struct bitsconv_data *bc) +{ + if (!bc) + { + return 0; + } + + if (bc->bitsconv_buf) + { + free(bc->bitsconv_buf); + bc->bitsconv_buf = NULL; + } + + free(bc); + + return 0; +} diff --git a/audioutils/alsa-lib/bits_convert.h b/audioutils/alsa-lib/bits_convert.h new file mode 100644 index 00000000000..e99adbaa491 --- /dev/null +++ b/audioutils/alsa-lib/bits_convert.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/bits_convert.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __BITS_CONVERT_H +#define __BITS_CONVERT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct bitsconv_data +{ + FAR void *bitsconv_buf; + int buf_size; + + snd_pcm_format_t src_format; + snd_pcm_format_t dst_format; + int channels; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int bitsconv_init(FAR struct bitsconv_data **bc, int channels, + snd_pcm_format_t src_format, snd_pcm_format_t dst_format, + int buf_size); + +int bitsconv_process(FAR struct bitsconv_data *bc, FAR const void *in_data, + int in_size, FAR void **out_data, FAR int *out_size); + +int bitsconv_release(FAR struct bitsconv_data *bc); + +#endif /* __BITS_CONVERT_H */ \ No newline at end of file diff --git a/audioutils/alsa-lib/channels_map.c b/audioutils/alsa-lib/channels_map.c new file mode 100644 index 00000000000..f8715e00ef9 --- /dev/null +++ b/audioutils/alsa-lib/channels_map.c @@ -0,0 +1,223 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/channels_map.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "channels_map.h" +#include "pcm_local.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int chmap_init(FAR struct chmap_data **cm, snd_pcm_format_t format, + int src_ch, int dst_ch, int buf_size) +{ + if (*cm != NULL) + { + return 0; + } + + if (format != SND_PCM_FORMAT_S16_LE && format != SND_PCM_FORMAT_S32_LE) + { + SND_ERR("format not supported"); + return -ENOTSUP; + } + + *cm = malloc(sizeof(struct chmap_data)); + if (*cm == NULL) + { + SND_ERR("chmap_ap_init failed"); + return -ENOMEM; + } + + (*cm)->chmap_buf = malloc(buf_size); + if ((*cm)->chmap_buf == NULL) + { + free(*cm); + *cm = NULL; + SND_ERR("malloc chmap_buf failed"); + return -ENOMEM; + } + + (*cm)->format = format; + (*cm)->src_ch = src_ch; + (*cm)->dest_ch = dst_ch; + (*cm)->buf_size = buf_size; + + return 0; +} + +static int do_channel_map_16(FAR struct chmap_data *cm, uint8_t src_ch, + uint8_t dst_ch, FAR const void *in_data, + int in_size) +{ + FAR const int16_t *in; + FAR int16_t *out; + int32_t tmp; + int out_len; + int iin; + int iout; + int i; + int j; + int z; + + out_len = in_size * dst_ch * sizeof(int16_t); + if (out_len > cm->buf_size) + { + SND_ERR("cm out_buf too small"); + return -EPERM; + } + + in = (const int16_t *)in_data; + out = (int16_t *)cm->chmap_buf; + for (i = 0; i < dst_ch; i++) + { + for (j = 0; j < in_size; j++) + { + tmp = 0; + iout = j * dst_ch + i; + for (z = 0; z < src_ch; z++) + { + iin = j * src_ch + z; + tmp += in[iin] / src_ch; + } + + if (tmp > 0x7fff) + { + out[iout] = 0x7fff; + } + else if (tmp < -0x8000) + { + out[iout] = -0x8000; + } + else + { + out[iout] = tmp; + } + } + } + + return 0; +} + +static int do_channel_map_32(FAR struct chmap_data *cm, uint8_t src_ch, + uint8_t dst_ch, FAR const void *in_data, + int in_size) +{ + FAR const int32_t *in; + FAR int32_t *out; + int32_t tmp; + int out_len; + int iin; + int iout; + int i; + int j; + int z; + + out_len = in_size * dst_ch * sizeof(int32_t); + if (out_len > cm->buf_size) + { + SND_ERR("cm out_buf too small"); + return -EPERM; + } + + in = (const int32_t *)in_data; + out = (int32_t *)cm->chmap_buf; + for (i = 0; i < dst_ch; i++) + { + for (j = 0; j < in_size; j++) + { + tmp = 0; + iout = j * dst_ch + i; + for (z = 0; z < src_ch; z++) + { + iin = j * src_ch + z; + tmp += in[iin] / src_ch; + } + + if (tmp > (int)0x7fffffff) + { + out[iout] = (int)0x7fffffff; + } + else if (tmp < (int)0x80000000) + { + out[iout] = (int)0x80000000; + } + else + { + out[iout] = tmp; + } + } + } + + return 0; +} + +int chmap_process(FAR struct chmap_data *cm, FAR const void *in_data, + int in_size, FAR void **out_data, int *out_size) +{ + if (!cm) + { + SND_ERR("chmap_ap_init not run"); + return -EPERM; + } + + if (cm->format == SND_PCM_FORMAT_S16_LE) + { + do_channel_map_16(cm, cm->src_ch, cm->dest_ch, in_data, in_size); + } + else if (cm->format == SND_PCM_FORMAT_S32_LE) + { + do_channel_map_32(cm, cm->src_ch, cm->dest_ch, in_data, in_size); + } + else + { + SND_ERR("unknown format"); + return -ENOTSUP; + } + + *out_data = cm->chmap_buf; + *out_size = in_size; + + return 0; +} + +int chmap_release(FAR struct chmap_data *cm) +{ + if (!cm) + { + return 0; + } + + if (cm->chmap_buf != NULL) + { + free(cm->chmap_buf); + cm->chmap_buf = NULL; + } + + free(cm); + + return 0; +} diff --git a/audioutils/alsa-lib/channels_map.h b/audioutils/alsa-lib/channels_map.h new file mode 100644 index 00000000000..d4b446a68dd --- /dev/null +++ b/audioutils/alsa-lib/channels_map.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/channels_map.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __CHANNELS_MAP_H +#define __CHANNELS_MAP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct chmap_data +{ + FAR void *chmap_buf; + int buf_size; + + snd_pcm_format_t format; + int src_ch; + int dest_ch; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int chmap_init(FAR struct chmap_data **cm, snd_pcm_format_t format, + int src_ch, int dst_ch, int chmap_buf_size); + +int chmap_process(FAR struct chmap_data *cm, FAR const void *in_data, + int in_size, FAR void **out_data, FAR int *out_size); + +int chmap_release(FAR struct chmap_data *cm); + +#endif /* __CHANNELS_MAP_H */ \ No newline at end of file diff --git a/audioutils/alsa-lib/error.c b/audioutils/alsa-lib/error.c new file mode 100644 index 00000000000..b9c5e38bc92 --- /dev/null +++ b/audioutils/alsa-lib/error.c @@ -0,0 +1,41 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/error.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +FAR const char *snd_strerror(int errnum) +{ + if (errnum < 0) + { + errnum = -errnum; + } + + return strerror(errnum); +} diff --git a/audioutils/alsa-lib/include/alsa_error.h b/audioutils/alsa-lib/include/alsa_error.h new file mode 100644 index 00000000000..fb1d0359a3e --- /dev/null +++ b/audioutils/alsa-lib/include/alsa_error.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/include/alsa_error.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ALSA_ERROR_H +#define __ALSA_ERROR_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +FAR const char *snd_strerror(int errnum); + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_ERROR_H */ \ No newline at end of file diff --git a/audioutils/alsa-lib/include/alsa_pcm.h b/audioutils/alsa-lib/include/alsa_pcm.h new file mode 100644 index 00000000000..fe0c4967afa --- /dev/null +++ b/audioutils/alsa-lib/include/alsa_pcm.h @@ -0,0 +1,487 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/include/alsa_pcm.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ALSA_PCM_H +#define __ALSA_PCM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Pre-processor Prototypes + ****************************************************************************/ + +#define SND_PCM_HW_PARAM_ACCESS 0 +#define SND_PCM_HW_PARAM_FORMAT 1 +#define SND_PCM_HW_PARAM_FIRST_MASK SND_PCM_HW_PARAM_ACCESS +#define SND_PCM_HW_PARAM_LAST_MASK SND_PCM_HW_PARAM_FORMAT +#define SND_PCM_HW_PARAM_SAMPLE_BITS 2 +#define SND_PCM_HW_PARAM_FRAME_BITS 3 +#define SND_PCM_HW_PARAM_CHANNELS 4 +#define SND_PCM_HW_PARAM_RATE 5 +#define SND_PCM_HW_PARAM_PERIOD_TIME 6 +#define SND_PCM_HW_PARAM_PERIOD_SIZE 7 +#define SND_PCM_HW_PARAM_PERIOD_BYTES 8 +#define SND_PCM_HW_PARAM_PERIODS 9 +#define SND_PCM_HW_PARAM_BUFFER_TIME 10 +#define SND_PCM_HW_PARAM_BUFFER_SIZE 11 +#define SND_PCM_HW_PARAM_BUFFER_BYTES 12 +#define SND_PCM_HW_PARAM_FIRST_RANGE SND_PCM_HW_PARAM_SAMPLE_BITS +#define SND_PCM_HW_PARAM_LAST_RANGE SND_PCM_HW_PARAM_BUFFER_BYTES +#define SND_PCM_HW_PARAM_FIRST_INTERVAL SND_PCM_HW_PARAM_ACCESS +#define SND_PCM_HW_PARAM_LAST_INTERVAL SND_PCM_HW_PARAM_BUFFER_BYTES + +#define SND_PCM_NONBLOCK 0x00000001 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Unsigned frames quantity */ + +typedef unsigned long snd_pcm_uframes_t; + +/* Signed frames quantity */ + +typedef long snd_pcm_sframes_t; + +typedef int snd_pcm_hw_param_t; + +/** PCM type */ + +typedef enum +{ + /** Kernel level PCM */ + + SND_PCM_TYPE_HW = 0, + + /** Direct Mixing plugin */ + + SND_PCM_TYPE_DMIX, + SND_PCM_TYPE_LAST = SND_PCM_TYPE_DMIX +} snd_pcm_type_t; + +/* PCM handle */ + +typedef struct snd_pcm_s snd_pcm_t; + +/* PCM stream (direction) */ + +typedef enum +{ + /* Playback stream */ + + SND_PCM_STREAM_PLAYBACK = 0, + + /* Capture stream */ + + SND_PCM_STREAM_CAPTURE, + SND_PCM_STREAM_LAST = SND_PCM_STREAM_CAPTURE +} snd_pcm_stream_t; + +/* PCM access type */ + +typedef enum +{ + /* mmap access with simple interleaved channels */ + + SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, + + /* mmap access with simple non interleaved channels */ + + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + + /* mmap access with complex placement */ + + SND_PCM_ACCESS_MMAP_COMPLEX, + + /* snd_pcm_readi/snd_pcm_writei access */ + + SND_PCM_ACCESS_RW_INTERLEAVED, + + /* snd_pcm_readn/snd_pcm_writen access */ + + SND_PCM_ACCESS_RW_NONINTERLEAVED, + SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED +} snd_pcm_access_t; + +/* PCM sample format */ + +typedef enum +{ + /* Unknown */ + + SND_PCM_FORMAT_UNKNOWN, + + /* Signed 8 bit */ + + SND_PCM_FORMAT_S8, + + /* Unsigned 8 bit */ + + SND_PCM_FORMAT_U8, + + /* Signed 16 bit Little Endian */ + + SND_PCM_FORMAT_S16_LE, + + /* Signed 16 bit Big Endian */ + + SND_PCM_FORMAT_S16_BE, + + /* Unsigned 16 bit Little Endian */ + + SND_PCM_FORMAT_U16_LE, + + /* Unsigned 16 bit Big Endian */ + + SND_PCM_FORMAT_U16_BE, + + /* Signed 24 bit Little Endian using low three bytes in 32-bit word */ + + SND_PCM_FORMAT_S24_LE, + + /* Signed 24 bit Big Endian using low three bytes in 32-bit word */ + + SND_PCM_FORMAT_S24_BE, + + /* Unsigned 24 bit Little Endian using low three bytes in 32-bit word */ + + SND_PCM_FORMAT_U24_LE, + + /* Unsigned 24 bit Big Endian using low three bytes in 32-bit word */ + + SND_PCM_FORMAT_U24_BE, + + /* Signed 32 bit Little Endian */ + + SND_PCM_FORMAT_S32_LE, + + /* Signed 32 bit Big Endian */ + + SND_PCM_FORMAT_S32_BE, + + /* Unsigned 32 bit Little Endian */ + + SND_PCM_FORMAT_U32_LE, + + /* Unsigned 32 bit Big Endian */ + + SND_PCM_FORMAT_U32_BE, + + /* only support little endian Signed 16 bit CPU endian */ + + SND_PCM_FORMAT_S16 = SND_PCM_FORMAT_S16_LE, + + /* Unsigned 16 bit CPU endian */ + + SND_PCM_FORMAT_U16 = SND_PCM_FORMAT_U16_LE, + + /* Signed 24 bit CPU endian */ + + SND_PCM_FORMAT_S24 = SND_PCM_FORMAT_S24_LE, + + /* Unsigned 24 bit CPU endian */ + + SND_PCM_FORMAT_U24 = SND_PCM_FORMAT_U24_LE, + + /* Signed 32 bit CPU endian */ + + SND_PCM_FORMAT_S32 = SND_PCM_FORMAT_S32_LE, + + /* Unsigned 32 bit CPU endian */ + + SND_PCM_FORMAT_U32 = SND_PCM_FORMAT_U32_LE, + + SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_U32_BE, +} snd_pcm_format_t; + +/* PCM state */ + +typedef enum +{ + /* Open */ + + SND_PCM_STATE_OPEN = 0, + + /* Setup installed */ + + SND_PCM_STATE_SETUP, + + /* Ready to start */ + + SND_PCM_STATE_PREPARED, + + /* Running */ + + SND_PCM_STATE_RUNNING, + + /* Stopped: underrun (playback) or overrun (capture) detected */ + + SND_PCM_STATE_XRUN, + + /* Draining: running (playback) or stopped (capture) */ + + SND_PCM_STATE_DRAINING, + + /* Paused */ + + SND_PCM_STATE_PAUSED, + + /* Hardware is suspended */ + + SND_PCM_STATE_SUSPENDED, + + /* Hardware is disconnected */ + + SND_PCM_STATE_DISCONNECTED, + SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED, + + /* Private - used internally in the library - do not use */ + + SND_PCM_STATE_PRIVATE1 = 1024 +} snd_pcm_state_t; + +typedef union +{ + struct + { + uint32_t min; + uint32_t max; + int openmin; /* whether the interval is left-open */ + int openmax; /* whether the interval is right-open */ + int integer; /* whether the value is integer or not */ + int empty; + } range; + uint32_t mask; +} snd_interval_t; + +typedef struct +{ + snd_interval_t intervals[SND_PCM_HW_PARAM_LAST_INTERVAL - + SND_PCM_HW_PARAM_FIRST_INTERVAL + 1]; +} snd_pcm_hw_params_t; + +typedef struct +{ + snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */ + snd_pcm_uframes_t start_threshold; /* min hw_avail frames for automatic start */ + snd_pcm_uframes_t stop_threshold; /* min avail frames for automatic stop */ + snd_pcm_uframes_t silence_size; /* silence block size */ + snd_pcm_uframes_t boundary; /* pointers wrap point */ +} snd_pcm_sw_params_t; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int snd_pcm_open(FAR snd_pcm_t **pcm, FAR const char *name, + snd_pcm_stream_t stream, int mode); +int snd_pcm_close(FAR snd_pcm_t *pcm); +int snd_pcm_prepare(FAR snd_pcm_t *pcm); +int snd_pcm_start(FAR snd_pcm_t *pcm); +int snd_pcm_pause(FAR snd_pcm_t *pcm, int enable); +snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm); +int snd_pcm_hwsync(snd_pcm_t *pcm); +int snd_pcm_delay(FAR snd_pcm_t *pcm, FAR snd_pcm_sframes_t *delayp); +int snd_pcm_drop(FAR snd_pcm_t *pcm); +int snd_pcm_drain(FAR snd_pcm_t *pcm); +int snd_pcm_resume(FAR snd_pcm_t *pcm); +snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm); +int snd_pcm_recover(FAR snd_pcm_t *pcm, int err, int silent); +int snd_pcm_wait(FAR snd_pcm_t *pcm, int timeout); + +int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm); +int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, + unsigned int space); +snd_pcm_sframes_t snd_pcm_writei(FAR snd_pcm_t *pcm, FAR const void *buffer, + snd_pcm_uframes_t size); + +int snd_pcm_set_params(FAR snd_pcm_t *pcm, snd_pcm_format_t format, + snd_pcm_access_t access, unsigned int channels, + unsigned int rate, int soft_resample, + unsigned int latency); +int snd_pcm_get_params(FAR snd_pcm_t *pcm, + FAR snd_pcm_uframes_t *buffer_size, + FAR snd_pcm_uframes_t *period_size); +int snd_pcm_get_sample_bits(snd_pcm_format_t format); +snd_pcm_sframes_t snd_pcm_bytes_to_frames(FAR snd_pcm_t *pcm, ssize_t bytes); +ssize_t snd_pcm_frames_to_bytes(FAR snd_pcm_t *pcm, + snd_pcm_sframes_t frames); + +FAR const char *snd_pcm_type_name(snd_pcm_type_t type); +FAR const char *snd_pcm_stream_name(const snd_pcm_stream_t stream); +FAR const char *snd_pcm_access_name(const snd_pcm_access_t _access); +FAR const char *snd_pcm_format_name(const snd_pcm_format_t format); +FAR const char *snd_pcm_format_description(const snd_pcm_format_t format); +FAR const char *snd_pcm_state_name(const snd_pcm_state_t state); + +/* volume control, nuttx private interface */ + +int snd_pcm_set_volume(FAR snd_pcm_t *pcm, int volume); /* volume: [0, 100] */ +int snd_pcm_set_volume_db(FAR snd_pcm_t *pcm, int db); /* db: dB * 100 */ + +int snd_pcm_set_vis_fifo(FAR snd_pcm_t *pcm, FAR const char *fifo_name); +int snd_pcm_close_vis_fifo(FAR snd_pcm_t *pcm); + +/* hw & sw define */ + +#define snd_pcm_alloca(ptr, type) \ + do { \ + *ptr = (FAR type *)alloca(sizeof(type)); \ + memset(*ptr, 0, sizeof(type)); \ + } while (0) + +#define snd_pcm_hw_params_alloca(ptr) snd_pcm_alloca(ptr, snd_pcm_hw_params_t) + +int snd_pcm_hw_params_any(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params); + +int snd_pcm_hw_params_set_format(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_format_t format); + +int snd_pcm_hw_params_set_channels(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int val); +int snd_pcm_hw_params_get_channels(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *val); + +int snd_pcm_hw_params_set_rate(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int val, int dir); +int snd_pcm_hw_params_set_rate_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir); +int snd_pcm_hw_params_get_rate(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir); + +int snd_pcm_hw_params_set_period_time(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int us, int dir); +int snd_pcm_hw_params_set_period_time_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *us, + FAR int *dir); +int snd_pcm_hw_params_get_period_time(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *us, FAR int *dir); + +int snd_pcm_hw_params_set_period_size(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_uframes_t val, int dir); +int snd_pcm_hw_params_set_period_size_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val, + FAR int *dir); +int snd_pcm_hw_params_get_period_size(FAR const snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val, + FAR int *dir); + +int snd_pcm_hw_params_set_periods(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int val, int dir); +int snd_pcm_hw_params_set_periods_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir); +int snd_pcm_hw_params_get_periods(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir); + +int snd_pcm_hw_params_set_buffer_size(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_uframes_t val); +int snd_pcm_hw_params_set_buffer_size_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val); +int snd_pcm_hw_params_get_buffer_size(FAR const snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val); + +int snd_pcm_hw_params_set_buffer_time(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int us); +int snd_pcm_hw_params_set_buffer_time_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *us, + FAR int *dir); +int snd_pcm_hw_params_get_buffer_time(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *us, FAR int *dir); + +int snd_pcm_hw_params_set_access(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_access_t access); + +int snd_pcm_hw_params(FAR snd_pcm_t *pcm, FAR snd_pcm_hw_params_t *params); + +#define snd_pcm_sw_params_alloca(ptr) snd_pcm_alloca(ptr, snd_pcm_sw_params_t) + +int snd_pcm_sw_params_current(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params); + +int snd_pcm_sw_params_set_start_threshold(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val); +int snd_pcm_sw_params_get_start_threshold(FAR snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val); + +int snd_pcm_sw_params_set_stop_threshold(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val); +int snd_pcm_sw_params_get_stop_threshold(FAR snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val); + +int snd_pcm_sw_params_set_silence_size(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val); +int snd_pcm_sw_params_get_silence_size(FAR snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val); + +int snd_pcm_sw_params_set_avail_min(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val); +int snd_pcm_sw_params_get_avail_min(FAR const snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val); + +int snd_pcm_sw_params_get_boundary(FAR const snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val); + +int snd_pcm_sw_params(FAR snd_pcm_t *pcm, FAR snd_pcm_sw_params_t *params); + +int snd_pcm_format_little_endian(snd_pcm_format_t format); +int snd_pcm_format_big_endian(snd_pcm_format_t format); +int snd_pcm_format_cpu_endian(snd_pcm_format_t format); + +int snd_pcm_dump(FAR snd_pcm_t *pcm); + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_PCM_H */ diff --git a/audioutils/alsa-lib/include/asoundlib.h b/audioutils/alsa-lib/include/asoundlib.h new file mode 100644 index 00000000000..780e1237af2 --- /dev/null +++ b/audioutils/alsa-lib/include/asoundlib.h @@ -0,0 +1,31 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/include/asoundlib.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ASOUNDLIB_H +#define __ASOUNDLIB_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#endif /* __ASOUNDLIB_H */ diff --git a/audioutils/alsa-lib/pcm.c b/audioutils/alsa-lib/pcm.c new file mode 100644 index 00000000000..85c8389cc1c --- /dev/null +++ b/audioutils/alsa-lib/pcm.c @@ -0,0 +1,808 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include "pcm_local.h" + +/**************************************************************************** + * Pre-processor Prototypes + ****************************************************************************/ + +#define PCMTYPE(v) [SND_PCM_TYPE_##v] = #v +#define STATE(v) [SND_PCM_STATE_##v] = #v +#define STREAM(v) [SND_PCM_STREAM_##v] = #v +#define READY(v) [SND_PCM_READY_##v] = #v +#define XRUN(v) [SND_PCM_XRUN_##v] = #v +#define SILENCE(v) [SND_PCM_SILENCE_##v] = #v +#define TSTAMP(v) [SND_PCM_TSTAMP_##v] = #v +#define ACCESS(v) [SND_PCM_ACCESS_##v] = #v +#define START(v) [SND_PCM_START_##v] = #v +#define HW_PARAM(v) [SND_PCM_HW_PARAM_##v] = #v +#define SW_PARAM(v) [SND_PCM_SW_PARAM_##v] = #v +#define FORMAT(v) [SND_PCM_FORMAT_##v] = #v +#define SUBFORMAT(v) [SND_PCM_SUBFORMAT_##v] = #v + +#define FORMATD(v, d) [SND_PCM_FORMAT_##v] = d +#define SUBFORMATD(v, d) [SND_PCM_SUBFORMAT_##v] = d + +#define CHECK_PCM_SETUP(pcm) \ + do \ + { \ + assert(pcm); \ + if (!(pcm)->setup) \ + { \ + SND_ERR("PCM not set up"); \ + return -EIO; \ + } \ + } \ + while (0) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int snd_pcm_dump_hw_setup(FAR snd_pcm_t *pcm) +{ + SND_INFO("stream : %s\n", snd_pcm_stream_name(pcm->stream)); + SND_INFO("access : %s\n", snd_pcm_access_name(pcm->access)); + SND_INFO("format : %s\n", snd_pcm_format_name(pcm->format)); + SND_INFO("channels : %u\n", pcm->channels); + SND_INFO("rate : %u\n", pcm->sample_rate); + SND_INFO("period : %u\n", pcm->periods); + SND_INFO("period_time : %u\n", pcm->period_time); + SND_INFO("period_bytes : %d\n", pcm->period_bytes); + SND_INFO("buffer_bytes : %d\n", pcm->buffer_bytes); + SND_INFO("period_frames: %ld\n", pcm->period_frames); + SND_INFO("buffer_frames: %ld\n", pcm->buffer_frames); + + return 0; +} + +static int snd_pcm_dump_sw_setup(FAR snd_pcm_t *pcm) +{ + SND_INFO("start_threshold : %ld\n", pcm->start_threshold); + + return 0; +} + +static int snd_pcm_dump_setup(FAR snd_pcm_t *pcm) +{ + snd_pcm_dump_hw_setup(pcm); + snd_pcm_dump_sw_setup(pcm); + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +const char *snd_pcm_name(snd_pcm_t *pcm) +{ + assert(pcm); + return pcm->name; +} + +snd_pcm_type_t snd_pcm_type(snd_pcm_t *pcm) +{ + assert(pcm); + return pcm->type; +} + +snd_pcm_stream_t snd_pcm_stream(snd_pcm_t *pcm) +{ + assert(pcm); + return pcm->stream; +} + +int snd_pcm_close(snd_pcm_t *pcm) +{ + int ret; + + assert(pcm); + ret = pcm->ops->close(pcm->ops_arg); + + snd_pcm_free(pcm); + return ret; +} + +int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) +{ + assert(pcm); + if (nonblock) + { + pcm->mode |= SND_PCM_NONBLOCK; + } + else + { + pcm->mode &= ~SND_PCM_NONBLOCK; + } + + return 0; +} + +snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm) +{ + assert(pcm); + + if (pcm->ops->state) + { + return pcm->ops->state(pcm->ops_arg); + } + + return pcm->state; +} + +int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->delay(pcm->ops_arg, delayp); +} + +int snd_pcm_resume(snd_pcm_t *pcm) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->resume(pcm->ops_arg); +} + +snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->avail_update(pcm->ops_arg); +} + +int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent) +{ + if (err > 0) + { + err = -err; + } + + if (err == -EINTR) + { + return 0; + } + + if (err == -EPIPE) + { + const char *s; + if (snd_pcm_stream(pcm) == SND_PCM_STREAM_PLAYBACK) + { + s = "underrun"; + } + else + { + s = "overrun"; + } + + err = snd_pcm_prepare(pcm); + if (err < 0) + { + SND_ERR("cannot recovery from %s, prepare failed: %s", s, + snd_strerror(err)); + return err; + } + + return 0; + } + + return err; +} + +int snd_pcm_prepare(snd_pcm_t *pcm) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->prepare(pcm->ops_arg); +} + +int snd_pcm_reset(snd_pcm_t *pcm) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->reset(pcm->ops_arg); +} + +int snd_pcm_start(snd_pcm_t *pcm) +{ + int ret; + CHECK_PCM_SETUP(pcm); + + snd_pcm_dump(pcm); + + if (pcm->ops->start) + { + return pcm->ops->start(pcm->ops_arg); + } + + if (pcm->state != SND_PCM_STATE_PREPARED) + { + return 0; + } + + ret = ioctl(pcm->fd, AUDIOIOC_START, 0); + if (ret < 0) + { + SND_ERR("start error:%d\n", ret); + return ret; + } + + pcm->state = SND_PCM_STATE_RUNNING; + pcm->running = true; + + return ret; +} + +int snd_pcm_drop(snd_pcm_t *pcm) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->drop(pcm->ops_arg); +} + +int snd_pcm_drain(snd_pcm_t *pcm) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->drain(pcm->ops_arg); +} + +int snd_pcm_pause(snd_pcm_t *pcm, int enable) +{ + int ret; + CHECK_PCM_SETUP(pcm); + + if (pcm->ops->pause) + { + return pcm->ops->pause(pcm->ops_arg, enable); + } + + if (!pcm->running) + { + return 0; + } + + ret = ioctl(pcm->fd, enable ? AUDIOIOC_PAUSE : AUDIOIOC_RESUME, 0); + if (ret < 0) + { + SND_ERR("pause:%d, ret:%d", enable, ret); + return ret; + } + + if (enable) + { + pcm->state = SND_PCM_STATE_PAUSED; + } + else + { + pcm->state = SND_PCM_STATE_RUNNING; + snd_pcm_reset(pcm); + } + + return ret; +} + +int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm) +{ + return 1; +} + +int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, + unsigned int space) +{ + assert(pcm && pfds); + return pcm->ops->poll_descriptors(pcm->ops_arg, pfds, space); +} + +snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, + snd_pcm_uframes_t size) +{ + CHECK_PCM_SETUP(pcm); + + return pcm->ops->writei(pcm->ops_arg, buffer, size); +} + +int snd_pcm_set_volume(FAR snd_pcm_t *pcm, int volume) +{ + if (volume < 0 || volume > 100) + { + return -EINVAL; + } + + pcm->volume = volume / 100.0; + SND_INFO("set volume:%f", pcm->volume); + + return 0; +} + +int snd_pcm_set_volume_db(FAR snd_pcm_t *pcm, int db) +{ + if (db > 0) + { + return -EINVAL; + } + + pcm->volume = powf(10.0, (double)db / 2000.0); + SND_INFO("set volume:%f", pcm->volume); + + return 0; +} + +int snd_pcm_set_vis_fifo(FAR snd_pcm_t *pcm, FAR const char *fifo_name) +{ + if (!fifo_name) + { + return -EINVAL; + } + + if (mkfifo(fifo_name, 0666) < 0 && errno != EEXIST) + { + SND_INFO("mkfifo error: %s\n", strerror(errno)); + return -errno; + } + + pcm->vis_fd = open(fifo_name, O_RDONLY | O_NONBLOCK, 0); + if (pcm->vis_fd < 0) + { + SND_INFO("open fifo error: %s\n", strerror(errno)); + return -errno; + } + + SND_INFO("open vis fifo:%s, %d", fifo_name, pcm->vis_fd); + + return 0; +} + +int snd_pcm_close_vis_fifo(FAR snd_pcm_t *pcm) +{ + if (pcm->vis_fd >= 0) + { + close(pcm->vis_fd); + pcm->vis_fd = -1; + } + + return 0; +} + +FAR const char *snd_pcm_stream_name(snd_pcm_stream_t stream) +{ + if (stream > SND_PCM_STREAM_LAST) + { + return NULL; + } + + const char *_snd_pcm_stream_names[] = + { + STREAM(PLAYBACK), + STREAM(CAPTURE), + }; + + return _snd_pcm_stream_names[stream]; +} + +FAR const char *snd_pcm_access_name(snd_pcm_access_t acc) +{ + if (acc > SND_PCM_ACCESS_LAST) + { + return NULL; + } + + const char *_snd_pcm_access_names[] = + { + ACCESS(MMAP_INTERLEAVED), ACCESS(MMAP_NONINTERLEAVED), + ACCESS(MMAP_COMPLEX), ACCESS(RW_INTERLEAVED), + ACCESS(RW_NONINTERLEAVED), + }; + + return _snd_pcm_access_names[acc]; +} + +FAR const char *snd_pcm_format_name(snd_pcm_format_t format) +{ + if (format > SND_PCM_FORMAT_LAST) + { + return NULL; + } + + const char *_snd_pcm_format_names[] = + { + FORMAT(S8), FORMAT(U8), FORMAT(S16_LE), FORMAT(S16_BE), + FORMAT(U16_LE), FORMAT(U16_BE), FORMAT(S24_LE), FORMAT(S24_BE), + FORMAT(U24_LE), FORMAT(U24_BE), FORMAT(S32_LE), FORMAT(S32_BE), + FORMAT(U32_LE), FORMAT(U32_BE), + }; + + return _snd_pcm_format_names[format]; +} + +FAR const char *snd_pcm_format_description(snd_pcm_format_t format) +{ + if (format > SND_PCM_FORMAT_LAST) + { + return NULL; + } + + const char *_snd_pcm_format_descriptions[] = + { + FORMATD(S8, "Signed 8 bit"), + FORMATD(U8, "Unsigned 8 bit"), + FORMATD(S16_LE, "Signed 16 bit Little Endian"), + FORMATD(S16_BE, "Signed 16 bit Big Endian"), + FORMATD(U16_LE, "Unsigned 16 bit Little Endian"), + FORMATD(U16_BE, "Unsigned 16 bit Big Endian"), + FORMATD(S24_LE, "Signed 24 bit Little Endian"), + FORMATD(S24_BE, "Signed 24 bit Big Endian"), + FORMATD(U24_LE, "Unsigned 24 bit Little Endian"), + FORMATD(U24_BE, "Unsigned 24 bit Big Endian"), + FORMATD(S32_LE, "Signed 32 bit Little Endian"), + FORMATD(S32_BE, "Signed 32 bit Big Endian"), + FORMATD(U32_LE, "Unsigned 32 bit Little Endian"), + FORMATD(U32_BE, "Unsigned 32 bit Big Endian"), + }; + + return _snd_pcm_format_descriptions[format]; +} + +FAR const char *snd_pcm_state_name(snd_pcm_state_t state) +{ + if (state > SND_PCM_STATE_LAST) + { + return NULL; + } + + const char *_snd_pcm_state_names[] = + { + STATE(OPEN), STATE(SETUP), STATE(PREPARED), + STATE(RUNNING), STATE(XRUN), STATE(DRAINING), + STATE(PAUSED), STATE(SUSPENDED), STATE(DISCONNECTED), + }; + + return _snd_pcm_state_names[state]; +} + +int snd_pcm_dump(FAR snd_pcm_t *pcm) +{ + assert(pcm); + snd_pcm_dump_setup(pcm); + if (pcm->ops->dump) + { + pcm->ops->dump(pcm->ops_arg); + } + + return 0; +} + +snd_pcm_sframes_t snd_pcm_bytes_to_frames(FAR snd_pcm_t *pcm, ssize_t bytes) +{ + CHECK_PCM_SETUP(pcm); + + return bytes / pcm->frame_bytes; +} + +ssize_t snd_pcm_frames_to_bytes(FAR snd_pcm_t *pcm, snd_pcm_sframes_t frames) +{ + CHECK_PCM_SETUP(pcm); + + return frames * pcm->frame_bytes; +} + +int snd_pcm_open(FAR snd_pcm_t **pcmp, FAR const char *name, + snd_pcm_stream_t stream, int mode) +{ + assert(pcmp && name); + + if (strcmp(name, "default") == 0) + { + name = "pcm0p"; + } + +#if defined(CONFIG_AUDIOUTILS_ALSA_LIB_DEVICE_DMIX) + return snd_pcm_dmix_open(pcmp, name, stream, mode); +#elif defined(CONFIG_AUDIOUTILS_ALSA_LIB_DEVICE_HW) + return snd_pcm_hw_open(pcmp, name, stream, mode); +#else + return -EPERM; +#endif +} + +int snd_pcm_new(FAR snd_pcm_t **pcmp, snd_pcm_type_t type, + FAR const char *name, snd_pcm_stream_t stream, int mode) +{ + FAR snd_pcm_t *pcm; + pcm = calloc(1, sizeof(*pcm)); + if (!pcm) + { + return -ENOMEM; + } + + pcm->type = type; + if (name) + { + pcm->name = strdup(name); + } + + pcm->stream = stream; + pcm->mode = mode; + pcm->ops_arg = pcm; + pcm->volume = 1.0; + pcm->dump_fd = -1; + pcm->vis_fd = -1; + *pcmp = pcm; + return 0; +} + +int snd_pcm_free(FAR snd_pcm_t *pcm) +{ + assert(pcm); + free(pcm->name); + free(pcm); + return 0; +} + +int snd_pcm_wait(FAR snd_pcm_t *pcm, int timeout) +{ + struct pollfd pfd; + int pollio; + int ret; + + ret = snd_pcm_poll_descriptors(pcm, &pfd, 1); + if (ret <= 0) + { + SND_ERR("invalid snd_pcm_poll_descriptors ret: %d", ret); + return -EIO; + } + + do + { + pollio = 0; + ret = poll(&pfd, 1, timeout); + if (ret < 0) + { + if (errno == EINTR) + continue; + return -errno; + } + + if (!ret) + { + break; + } + + if (pfd.revents & (POLLERR | POLLNVAL)) + { + /* check more precisely */ + + switch (snd_pcm_state(pcm)) + { + case SND_PCM_STATE_XRUN: + return -EPIPE; + case SND_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case SND_PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EIO; + } + } + + if ((pfd.revents & (POLLIN | POLLOUT)) == 0) + { + continue; + } + + pollio++; + } + while (!pollio); + + return ret > 0 ? 1 : 0; +} + +int snd_pcm_get_sample_bits(snd_pcm_format_t format) +{ + switch (format) + { + case SND_PCM_FORMAT_U32: + case SND_PCM_FORMAT_S32: + return 32; + case SND_PCM_FORMAT_U24: + case SND_PCM_FORMAT_S24: + return 24; + case SND_PCM_FORMAT_U16: + case SND_PCM_FORMAT_S16: + return 16; + default: + return 8; + } +} + +int snd_pcm_set_params(FAR snd_pcm_t *pcm, snd_pcm_format_t format, + snd_pcm_access_t access, unsigned int channels, + unsigned int rate, int soft_resample, + unsigned int latency) +{ + SND_INFO("format:%d, ch:%d, rate:%d", format, channels, rate); + assert(pcm); + + pcm->format = format; + pcm->channels = channels; + pcm->sample_rate = rate; + + pcm->state = SND_PCM_STATE_SETUP; + + return 0; +} + +int snd_pcm_get_params(FAR snd_pcm_t *pcm, + FAR snd_pcm_uframes_t *buffer_size, + FAR snd_pcm_uframes_t *period_size) +{ + assert(pcm); + + *period_size = pcm->period_frames; + *buffer_size = pcm->buffer_frames; + + return 0; +} + +int snd_pcm_format_little_endian(snd_pcm_format_t format) +{ + switch (format) + { + case SND_PCM_FORMAT_S16_LE: + case SND_PCM_FORMAT_U16_LE: + case SND_PCM_FORMAT_S24_LE: + case SND_PCM_FORMAT_U24_LE: + case SND_PCM_FORMAT_S32_LE: + case SND_PCM_FORMAT_U32_LE: + return 1; + case SND_PCM_FORMAT_S16_BE: + case SND_PCM_FORMAT_U16_BE: + case SND_PCM_FORMAT_S24_BE: + case SND_PCM_FORMAT_U24_BE: + case SND_PCM_FORMAT_S32_BE: + case SND_PCM_FORMAT_U32_BE: + return 0; + default: + return -EINVAL; + } +} + +int snd_pcm_format_big_endian(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_little_endian(format); + if (val < 0) + { + return val; + } + + return !val; +} + +int snd_pcm_format_cpu_endian(snd_pcm_format_t format) +{ + return snd_pcm_format_little_endian(format); +} + +void snd_pcm_deinit(FAR snd_pcm_t *pcm) +{ + if (pcm->fd < 0) + { + return; + } + + if (pcm->mq >= 0) + { + ioctl(pcm->fd, AUDIOIOC_UNREGISTERMQ, 0); + + mq_close(pcm->mq); + pcm->mq = -1; + + mq_unlink(pcm->mqname); + } + + if (pcm->dump_fd >= 0) + { + close(pcm->dump_fd); + pcm->dump_fd = -1; + } + + if (pcm->vis_fd >= 0) + { + close(pcm->vis_fd); + pcm->vis_fd = -1; + } + + ioctl(pcm->fd, AUDIOIOC_RELEASE, 0); + close(pcm->fd); +} + +int snd_pcm_init(FAR snd_pcm_t *pcm) +{ + char path[32]; + int ret; + + struct mq_attr attr = { + .mq_maxmsg = 16, + .mq_msgsize = sizeof(struct audio_msg_s), + }; + + /* open device */ + + snprintf(path, sizeof(path), CONFIG_AUDIOUTILS_ALSA_LIB_DEV_PATH "/%s", + pcm->name); + pcm->fd = open(path, O_RDWR | O_CLOEXEC); + if (pcm->fd < 0) + { + return -errno; + } + + /* reserve */ + + ret = ioctl(pcm->fd, AUDIOIOC_RESERVE, 0); + if (ret < 0) + { + goto out; + } + + /* create message queue */ + + if (pcm->type != SND_PCM_TYPE_DMIX) + { + snprintf(pcm->mqname, sizeof(pcm->mqname), "/tmp/%p", pcm); + pcm->mq = + mq_open(pcm->mqname, O_RDWR | O_CREAT | O_CLOEXEC, 0644, &attr); + if (pcm->mq < 0) + { + ret = -errno; + goto out; + } + + ret = ioctl(pcm->fd, AUDIOIOC_REGISTERMQ, pcm->mq); + if (ret < 0) + { + goto out; + } + } + else + { + pcm->mq = -1; + } + + return 0; + +out: + snd_pcm_deinit(pcm); + return ret; +} diff --git a/audioutils/alsa-lib/pcm_dmix.c b/audioutils/alsa-lib/pcm_dmix.c new file mode 100644 index 00000000000..43ec2c72044 --- /dev/null +++ b/audioutils/alsa-lib/pcm_dmix.c @@ -0,0 +1,1235 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_dmix.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include "pcm_dmix.h" +#include "pcm_dmix_generic.h" + +/**************************************************************************** + * Pre-processor Prototypes + ****************************************************************************/ + +#define RESAMPLE_IO_FORMART SND_PCM_FORMAT_S16_LE + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int snd_pcm_dmix_bps_to_format(int bps) +{ + switch (bps) + { + case 16: + return SND_PCM_FORMAT_S16_LE; + case 32: + return SND_PCM_FORMAT_S32_LE; + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static int snd_pcm_dmix_configure(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + struct audio_caps_desc_s caps_desc; + struct ap_buffer_info_s buf_info; + int sample_bits; + int frame_bytes; + int ret; + + if (dmix->shmptr->format == 0) + { + dmix->shmptr->format = + CONFIG_AUDIOUTILS_ALSA_LIB_OUTPUT_FORMAT + ? snd_pcm_dmix_bps_to_format( + CONFIG_AUDIOUTILS_ALSA_LIB_OUTPUT_FORMAT) + : pcm->format; + + dmix->shmptr->channels = + CONFIG_AUDIOUTILS_ALSA_LIB_OUTPUT_CHANNELS + ? CONFIG_AUDIOUTILS_ALSA_LIB_OUTPUT_CHANNELS + : pcm->channels; + + dmix->shmptr->sample_rate = + CONFIG_AUDIOUTILS_ALSA_LIB_OUTPUT_RATE + ? CONFIG_AUDIOUTILS_ALSA_LIB_OUTPUT_RATE + : pcm->sample_rate; + } + + sample_bits = snd_pcm_get_sample_bits(dmix->shmptr->format); + frame_bytes = sample_bits / 8 * dmix->shmptr->channels; + + memset(&caps_desc, 0, sizeof(caps_desc)); + caps_desc.caps.ac_len = sizeof(struct audio_caps_s); + caps_desc.caps.ac_type = pcm->stream == SND_PCM_STREAM_PLAYBACK + ? AUDIO_TYPE_OUTPUT + : AUDIO_TYPE_INPUT; + caps_desc.caps.ac_channels = dmix->shmptr->channels; + caps_desc.caps.ac_chmap = 0; + caps_desc.caps.ac_controls.hw[0] = dmix->shmptr->sample_rate; + caps_desc.caps.ac_controls.b[3] = dmix->shmptr->sample_rate >> 16; + caps_desc.caps.ac_controls.b[2] = sample_bits; + caps_desc.caps.ac_subtype = AUDIO_FMT_PCM; + + ret = ioctl(pcm->fd, AUDIOIOC_CONFIGURE, &caps_desc); + if (ret < 0) + { + return ret; + } + + SND_INFO("configure. ch:%d, rate:%d, format:%d", dmix->shmptr->channels, + dmix->shmptr->sample_rate, dmix->shmptr->format); + + /* try set recommand params */ + + if (dmix->shmptr->period_frames == 0) + { + dmix->periods = pcm->periods ? pcm->periods : SND_PCM_DEFAULT_PERIODS; + if (pcm->period_frames) + { + dmix->period_bytes = pcm->period_frames * frame_bytes; + } + else + { + pcm->period_time = pcm->period_time ? pcm->period_time + : SND_PCM_DEFAULT_PERIOD_TIME; + dmix->period_bytes = pcm->period_time * dmix->shmptr->sample_rate / + 1000 / 1000 * frame_bytes; + } + } + else + { + dmix->periods = + dmix->shmptr->buffer_frames / dmix->shmptr->period_frames; + dmix->period_bytes = dmix->shmptr->period_frames * frame_bytes; + } + + buf_info.nbuffers = dmix->periods; + buf_info.buffer_size = dmix->period_bytes; + ioctl(pcm->fd, AUDIOIOC_SETBUFFERINFO, &buf_info); + + ret = ioctl(pcm->fd, AUDIOIOC_GETBUFFERINFO, &buf_info); + if (ret >= 0) + { + dmix->periods = buf_info.nbuffers; + dmix->period_bytes = buf_info.buffer_size; + } + + dmix->period_frames = dmix->period_bytes / frame_bytes; + dmix->buffer_frames = dmix->period_frames * dmix->periods; + dmix->shmptr->period_frames = dmix->period_frames; + dmix->shmptr->buffer_frames = dmix->buffer_frames; + + SND_INFO("get buffer info. n:%d, size:%d", dmix->periods, + dmix->period_bytes); + + return 0; +} + +static int snd_pcm_dmix_share_info_open(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int len = sizeof(snd_pcm_dmix_share_t); + + dmix->shmptr = + mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, pcm->fd, 0); + if (dmix->shmptr == MAP_FAILED) + { + SND_ERR("mmap error:%d, len:%d", -errno, len); + return -errno; + } + + dmix->shmptr->refs++; + return 0; +} + +static int snd_pcm_dmix_share_info_close(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int len = dmix->buffer_bytes; + + dmix->shmptr->refs--; + if (dmix->mixer.mix_buffer != MAP_FAILED && + munmap(dmix->mixer.mix_buffer, len) < 0) + { + SND_ERR("munmap error:%d, len:%d", -errno, len); + return -errno; + } + + dmix->mixer.mix_buffer = MAP_FAILED; + return 0; +} + +static int snd_pcm_dmix_mix_buffer_open(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int len = dmix->buffer_bytes; + + dmix->mixer.mix_buffer = + mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, pcm->fd, 0); + if (dmix->mixer.mix_buffer == MAP_FAILED) + { + SND_ERR("mmap error:%d, len:%d", -errno, len); + return -errno; + } + + return 0; +} + +static int snd_pcm_dmix_mix_buffer_close(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int len = dmix->buffer_bytes; + + if (dmix->mixer.mix_buffer != MAP_FAILED && + munmap(dmix->mixer.mix_buffer, len) < 0) + { + SND_ERR("munmap error:%d, len:%d", -errno, len); + return -errno; + } + + dmix->mixer.mix_buffer = MAP_FAILED; + return 0; +} + +static int snd_pcm_dmix_hw_ptr_open(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int len = sizeof(snd_pcm_mmap_status_t); + + dmix->hw_ptr = + mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, pcm->fd, 0); + if (dmix->hw_ptr == MAP_FAILED) + { + SND_ERR("mmap error:%d, len:%d", -errno, len); + return -errno; + } + + return 0; +} + +static int snd_pcm_dmix_hw_ptr_close(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int len = sizeof(snd_pcm_mmap_status_t); + + if (dmix->hw_ptr != MAP_FAILED && munmap(dmix->hw_ptr, len) < 0) + { + SND_ERR("munmap error:%d, len:%d", -errno, len); + return -errno; + } + + dmix->hw_ptr = MAP_FAILED; + return 0; +} + +int snd_pcm_dmix_semaphore_open(FAR snd_pcm_dmix_t *dmix) +{ + dmix->semid = sem_open(dmix->ipc_key, O_CREAT | O_EXCL, 0666, 1); + if (dmix->semid == SEM_FAILED) + { + if (errno == EEXIST) + { + dmix->semid = sem_open(dmix->ipc_key, 0); + if (dmix->semid == SEM_FAILED) + { + SND_ERR("sem_open error:%d", -errno); + return -errno; + } + } + else + { + SND_ERR("sem_open error:%d", -errno); + return -errno; + } + } + + return 0; +} + +static int snd_pcm_dmix_semaphore_close(FAR snd_pcm_dmix_t *dmix) +{ + if (dmix->semid != SEM_FAILED) + { + sem_close(dmix->semid); + dmix->semid = SEM_FAILED; + } + + return 0; +} + +static void mix_areas(FAR snd_pcm_t *pcm, FAR const void *src_areas, + FAR void *dst_areas, snd_pcm_uframes_t size) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + unsigned int sample_size; + mix_areas_t *do_mix_areas; + + switch (dmix->format) + { + case SND_PCM_FORMAT_S16_LE: + case SND_PCM_FORMAT_S16_BE: + sample_size = 2; + do_mix_areas = (mix_areas_t *)dmix->mixer.mix_areas_16; + break; + case SND_PCM_FORMAT_S32_LE: + case SND_PCM_FORMAT_S32_BE: + sample_size = 4; + do_mix_areas = (mix_areas_t *)dmix->mixer.mix_areas_32; + break; + case SND_PCM_FORMAT_S24_LE: + sample_size = 3; + do_mix_areas = (mix_areas_t *)dmix->mixer.mix_areas_24; + break; + case SND_PCM_FORMAT_U8: + sample_size = 1; + do_mix_areas = (mix_areas_t *)dmix->mixer.mix_areas_u8; + break; + default: + return; + } + + do_mix_areas(size * dmix->channels, (unsigned char *)dst_areas, + (unsigned char *)src_areas, sample_size, sample_size); +} + +static inline int dmix_down_sem(FAR snd_pcm_dmix_t *dmix) +{ + int ret; + while ((ret = sem_wait(dmix->semid) != 0)) + { + if (errno != EINTR) + { + return ret; + } + } + + return ret; +} + +static inline int dmix_up_sem(FAR snd_pcm_dmix_t *dmix) +{ + return sem_post(dmix->semid); +} + +static int snd_pcm_dmix_appl_reset(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int ret; + + ret = ioctl(pcm->fd, AUDIOIOC_PTR_APPL, 0); + if (ret < 0) + { + SND_ERR("ioctl failed: %d", ret); + return ret; + } + + dmix->appl_ptr = dmix->hw_ptr->read_head; + dmix->appl_reset = dmix->appl_ptr; + SND_DEBUG("[%ld %ld %ld]", dmix->hw_ptr->read_tail, + dmix->hw_ptr->read_head, dmix->appl_ptr); + return 0; +} + +static int snd_pcm_dmix_appl_forward(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int ret; + + ret = ioctl(pcm->fd, AUDIOIOC_PTR_APPL, 1); + if (ret < 0) + { + SND_ERR("ioctl failed:%d", ret); + return ret; + } + + dmix->appl_ptr++; + return 0; +} + +static snd_pcm_uframes_t snd_pcm_dmix_playback_avail(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_sframes_t used; + + if (dmix->appl_ptr < dmix->hw_ptr->read_head || + (pcm->state == SND_PCM_STATE_RUNNING && + dmix->appl_ptr > dmix->appl_reset && + dmix->appl_ptr <= dmix->hw_ptr->read_tail)) + { + pcm->state = SND_PCM_STATE_XRUN; + return -EPIPE; + } + + used = (dmix->appl_ptr - dmix->hw_ptr->read_tail) * dmix->period_frames; + + return dmix->buffer_frames - used; +} + +static snd_pcm_sframes_t snd_pcm_dmix_playback_hw_avail(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_sframes_t avail; + + avail = (dmix->appl_ptr - dmix->hw_ptr->read_head) * dmix->period_frames; + if (avail < 0) + { + return 0; + } + + return avail; +} + +static snd_pcm_uframes_t snd_pcm_dmix_written(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_uframes_t written; + + written = (dmix->appl_ptr - dmix->appl_reset) * pcm->period_frames; + + return written; +} + +static int snd_pcm_dmix_offset(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int offset; + + offset = dmix->appl_ptr % dmix->periods * dmix->period_bytes; + + return offset; +} + +static int snd_pcm_dmix_delay(FAR snd_pcm_t *pcm, + FAR snd_pcm_sframes_t *delayp) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int ret; + + switch (pcm->state) + { + case SND_PCM_STATE_DRAINING: + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_SUSPENDED: + ret = ioctl(pcm->fd, AUDIOIOC_GETLATENCY, delayp); + *delayp += snd_pcm_dmix_playback_hw_avail(pcm); + *delayp = *delayp * pcm->sample_rate / dmix->sample_rate; + *delayp += pcm->last_buffer.nframes; + SND_DEBUG("get delay:%ld", *delayp); + return ret; + case SND_PCM_STATE_XRUN: + return -EPIPE; + case SND_PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EBADFD; + } +} + +static int snd_pcm_dmix_prepare(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int max_buf_len; + int ret; + + SND_DEBUG("enter, pcm->state:%d", pcm->state); + + if (pcm->state == SND_PCM_STATE_XRUN) + { + snd_pcm_dmix_appl_reset(pcm); + pcm->state = SND_PCM_STATE_PREPARED; + return 0; + } + + if (pcm->state != SND_PCM_STATE_SETUP) + { + return -EPERM; + } + + ret = dmix_down_sem(dmix); + if (ret < 0) + { + SND_ERR("down sem fail, ret:%d, errno:%d", ret, errno); + return ret; + } + + ret = snd_pcm_init(pcm); + if (ret < 0) + { + SND_ERR("snd_pcm_dmix_init fail"); + goto err; + } + + ret = snd_pcm_dmix_share_info_open(pcm); + if (ret < 0) + { + SND_ERR("unable to create IPC shm instance"); + goto err; + } + + if (dmix->shmptr->refs == 1) + { + ret = snd_pcm_dmix_configure(pcm); + if (ret < 0) + { + SND_ERR("snd_pcm_dmix_configure fail"); + goto err; + } + } + else + { + dmix->period_frames = dmix->shmptr->period_frames; + dmix->buffer_frames = dmix->shmptr->buffer_frames; + } + + dmix->format = dmix->shmptr->format; + dmix->channels = dmix->shmptr->channels; + dmix->sample_rate = dmix->shmptr->sample_rate; + dmix->sample_bits = snd_pcm_get_sample_bits(dmix->format); + dmix->frame_bytes = dmix->sample_bits / 8 * dmix->channels; + dmix->period_bytes = dmix->period_frames * dmix->frame_bytes; + dmix->buffer_bytes = dmix->buffer_frames * dmix->frame_bytes; + dmix->periods = dmix->buffer_frames / dmix->period_frames; + dmix->period_time = dmix->period_frames * 1000 * 1000 / dmix->sample_rate; + + pcm->sample_bits = snd_pcm_get_sample_bits(pcm->format); + pcm->frame_bytes = pcm->sample_bits / 8 * pcm->channels; + pcm->periods = dmix->periods; + pcm->period_frames = ceilf((float)dmix->period_frames * pcm->sample_rate / + dmix->sample_rate); + pcm->buffer_frames = pcm->period_frames * pcm->periods; + pcm->period_bytes = pcm->period_frames * pcm->frame_bytes; + pcm->buffer_bytes = pcm->buffer_frames * pcm->frame_bytes; + pcm->period_time = pcm->period_frames * 1000 * 1000 / pcm->sample_rate; + pcm->start_threshold = + (dmix->shmptr->refs == 1) ? pcm->buffer_frames : pcm->period_frames; + + pcm->last_buffer.data = malloc(pcm->period_bytes); + if (!pcm->last_buffer.data) + { + SND_ERR("malloc last_buffer.data fail"); + ret = -ENOMEM; + goto err; + } + + pcm->last_buffer.nmaxframes = pcm->period_frames; + pcm->last_buffer.nframes = 0; + + SND_DEBUG("[pcm:%d %d %d][dmix:%d %d %d]", pcm->format, pcm->channels, + pcm->sample_rate, dmix->format, dmix->channels, + dmix->sample_rate); + + max_buf_len = MAX(dmix->period_bytes, pcm->period_bytes); + + /* pcm->format -> s16 */ + + if (pcm->format != RESAMPLE_IO_FORMART) + { + ret = bitsconv_init(&dmix->bc_s16, pcm->channels, pcm->format, + RESAMPLE_IO_FORMART, max_buf_len); + if (ret < 0) + { + SND_ERR("Failed to initialize bitconverter: %d\n", ret); + goto err; + } + } + + /* pcm->channels -> dmix->channels */ + + if (dmix->channels != pcm->channels) + { + ret = chmap_init(&dmix->cm, RESAMPLE_IO_FORMART, pcm->channels, + dmix->channels, max_buf_len); + if (ret < 0) + { + SND_ERR("Failed to initialize channel map: %d\n", ret); + goto err; + } + } + + /* pcm->sample_rate -> dmix->sample_rate */ + + if (dmix->sample_rate != pcm->sample_rate) + { + dmix->resampler = speex_resampler_init( + dmix->channels, pcm->sample_rate, dmix->sample_rate, 0, &ret); + if (ret != RESAMPLER_ERR_SUCCESS) + { + SND_ERR("Failed to initialize resampler: %s\n", + speex_resampler_strerror(ret)); + ret = -ret; + goto err; + } + + dmix->resmp_out = malloc(dmix->period_bytes); + if (!dmix->resmp_out) + { + SND_ERR("Failed to mallo resmp_out\n"); + ret = -ENOMEM; + goto err; + } + } + + /* s16 -> dmix->format */ + + if (dmix->format != RESAMPLE_IO_FORMART) + { + ret = bitsconv_init(&dmix->bc, dmix->channels, RESAMPLE_IO_FORMART, + dmix->format, dmix->period_bytes); + if (ret < 0) + { + SND_ERR("Failed to initialize bitconverter: %d\n", ret); + goto err; + } + } + + /* shm2 ringbuffer */ + + ret = snd_pcm_dmix_mix_buffer_open(pcm); + if (ret < 0) + { + SND_ERR("unable to initialize sum ring buffer"); + goto err; + } + + ret = snd_pcm_dmix_hw_ptr_open(pcm); + if (ret < 0) + { + SND_ERR("unable to initialize mmap status"); + goto err; + } + + generic_mixer_init(dmix); + pcm->state = SND_PCM_STATE_PREPARED; + dmix_up_sem(dmix); + return 0; + +err: + if (dmix->hw_ptr != MAP_FAILED) + { + snd_pcm_dmix_hw_ptr_close(pcm); + } + + if (dmix->mixer.mix_buffer != MAP_FAILED) + { + snd_pcm_dmix_mix_buffer_close(pcm); + } + + if (dmix->shmptr != MAP_FAILED) + { + snd_pcm_dmix_share_info_close(pcm); + } + + if (dmix->bc_s16) + { + bitsconv_release(dmix->bc_s16); + dmix->bc_s16 = NULL; + } + + if (dmix->bc) + { + bitsconv_release(dmix->bc); + dmix->bc = NULL; + } + + if (dmix->cm) + { + chmap_release(dmix->cm); + dmix->cm = NULL; + } + + if (dmix->resampler) + { + speex_resampler_destroy(dmix->resampler); + dmix->resampler = NULL; + } + + if (dmix->resmp_out) + { + free(dmix->resmp_out); + } + + snd_pcm_deinit(pcm); + dmix_up_sem(dmix); + SND_ERR("fail ret:%d", ret); + return ret; +} + +static int snd_pcm_dmix_reset(FAR snd_pcm_t *pcm) +{ + return snd_pcm_dmix_appl_reset(pcm); +} + +static void snd_pcm_dmix_dump(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + SND_INFO("format : %s\n", snd_pcm_format_name(dmix->format)); + SND_INFO("channels : %u\n", dmix->channels); + SND_INFO("rate : %u\n", dmix->sample_rate); + SND_INFO("period : %u\n", dmix->periods); + SND_INFO("period_time : %u\n", dmix->period_time); + SND_INFO("period_bytes : %d\n", dmix->period_bytes); + SND_INFO("buffer_bytes : %d\n", dmix->buffer_bytes); + SND_INFO("period_frames: %ld\n", dmix->period_frames); + SND_INFO("buffer_frames: %ld\n", dmix->buffer_frames); +} + +static int snd_pcm_dmix_drop(FAR snd_pcm_t *pcm) +{ + return 0; +} + +static int snd_pcm_dmix_drain(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int ret; + + if (pcm->mode & SND_PCM_NONBLOCK || pcm->state != SND_PCM_STATE_RUNNING) + { + return -EPERM; + } + + while (snd_pcm_dmix_playback_avail(pcm) < dmix->buffer_frames) + { + ret = snd_pcm_wait(pcm, -1); + if (ret == -EPIPE) + { + break; + } + } + + return 0; +} + +static int snd_pcm_dmix_close(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + int ret; + + SND_INFO("enter"); + + ret = dmix_down_sem(dmix); + if (ret < 0) + { + SND_ERR("down sem fail, ret:%d, errno:%d", ret, errno); + return ret; + } + + if (pcm->running) + { + ioctl(pcm->fd, AUDIOIOC_STOP, 0); + pcm->running = false; + } + + snd_pcm_deinit(pcm); + + if (dmix->mixer.mix_buffer != MAP_FAILED) + { + snd_pcm_dmix_mix_buffer_close(pcm); + } + + if (dmix->shmptr != MAP_FAILED) + { + snd_pcm_dmix_share_info_close(pcm); + } + + if (pcm->last_buffer.data) + { + free(pcm->last_buffer.data); + } + + if (dmix->bc_s16) + { + bitsconv_release(dmix->bc_s16); + } + + if (dmix->bc) + { + bitsconv_release(dmix->bc); + } + + if (dmix->cm) + { + chmap_release(dmix->cm); + } + + if (dmix->resampler) + { + speex_resampler_destroy(dmix->resampler); + } + + if (dmix->resmp_out) + { + free(dmix->resmp_out); + } + + dmix_up_sem(dmix); + snd_pcm_dmix_semaphore_close(dmix); + pcm->private_data = NULL; + free(dmix); + return 0; +} + +int snd_pcm_dmix_resume(FAR snd_pcm_t *pcm) +{ + if (pcm->state == SND_PCM_STATE_XRUN) + { + snd_pcm_dmix_appl_reset(pcm); + pcm->state = SND_PCM_STATE_PREPARED; + } + + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dmix_avail_update(FAR snd_pcm_t *pcm) +{ + snd_pcm_sframes_t avail; + + if (pcm->state == SND_PCM_STATE_XRUN) + { + return -EPIPE; + } + + avail = snd_pcm_dmix_playback_avail(pcm); + + if (avail < 0 && !pcm->running) + { + snd_pcm_dmix_appl_reset(pcm); + avail = snd_pcm_dmix_playback_avail(pcm); + } + + return avail; +} + +static void snd_pcm_dmix_raw_pcm_dump(FAR snd_pcm_t *pcm) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + time_t current_time; + char dump_file[64]; + char time_str[16]; + struct tm tm; + int result; + + result = access(CONFIG_AUDIOUTILS_ALSA_LIB_DUMP_FLAG, F_OK); + + if (result == 0 && pcm->dump_fd < 0) + { + time(¤t_time); + localtime_r(¤t_time, &tm); + strftime(time_str, 64, "%H-%M-%S", &tm); + + snprintf(dump_file, sizeof(dump_file), "%s/%d_%d-%d-%d_%s.pcm", + CONFIG_AUDIOUTILS_ALSA_LIB_DUMP_PATH, getpid(), dmix->format, + dmix->sample_rate, dmix->channels, time_str); + + pcm->dump_fd = open(dump_file, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (pcm->dump_fd < 0) + { + SND_ERR("open dump file fail, errno:%d, file:%s", -errno, + dump_file); + } + } + + if (result != 0 && pcm->dump_fd >= 0) + { + close(pcm->dump_fd); + pcm->dump_fd = -1; + } +} + +static snd_pcm_sframes_t snd_pcm_dmix_write_period(FAR snd_pcm_t *pcm, + const void *buffer) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + FAR const uint8_t *in_data = buffer; + snd_pcm_uframes_t xfer = 0; + snd_pcm_sframes_t ret = 0; + snd_pcm_uframes_t in_frames; + snd_pcm_uframes_t out_frames; + snd_pcm_uframes_t written; + FAR uint8_t *out_data; + uint32_t in_len; + uint32_t out_len; + int out_size; + int offset; + + SND_DEBUG("enter"); + + in_frames = pcm->period_frames; + out_frames = dmix->period_frames; + + if (dmix->bc_s16) + { + ret = bitsconv_process(dmix->bc_s16, in_data, in_frames, + (void **)&out_data, &out_size); + if (ret < 0) + { + SND_ERR("bitsconv err, ret:%ld", ret); + goto end; + } + + in_data = out_data; + } + + if (dmix->cm) + { + ret = chmap_process(dmix->cm, in_data, in_frames, (void **)&out_data, + &out_size); + if (ret < 0) + { + SND_ERR("chmap err, ret:%ld", ret); + goto end; + } + + in_data = out_data; + } + + if (dmix->resampler) + { + in_len = in_frames * pcm->channels; + out_len = out_frames * dmix->channels; + ret = speex_resampler_process_interleaved_int( + dmix->resampler, (int16_t *)in_data, (spx_uint32_t *)&in_len, + (int16_t *)dmix->resmp_out, (spx_uint32_t *)&out_len); + if (ret != RESAMPLER_ERR_SUCCESS) + { + ret = -ret; + SND_ERR("resample err, ret:%ld", ret); + goto end; + } + + in_data = (const uint8_t *)dmix->resmp_out; + } + + if (dmix->bc) + { + ret = bitsconv_process(dmix->bc, in_data, out_frames, + (void **)&out_data, &out_size); + if (ret < 0) + { + SND_ERR("bitsconv err, ret:%ld", ret); + goto end; + } + + in_data = out_data; + } + + offset = snd_pcm_dmix_offset(pcm); + + SND_DEBUG("offset:%d, [frame:%ld, forward:%.2f], [%ld %d %ld]", offset, + out_frames, (float)out_frames / dmix->period_frames, + dmix->appl_ptr, pcm->periods, dmix->period_frames); + + if (pcm->vis_fd >= 0) + { + write(pcm->vis_fd, in_data, out_frames * dmix->frame_bytes); + } + + if (pcm->volume != 1.0) + { + snd_pcm_softvol_scalar(pcm, pcm->last_buffer.data, in_data, + out_frames * dmix->channels); + in_data = pcm->last_buffer.data; + } + + ret = dmix_down_sem(dmix); + if (ret < 0) + { + SND_ERR("down sem fail, ret:%ld, errno:%d", ret, errno); + return ret; + } + + mix_areas(pcm, in_data, dmix->mixer.mix_buffer + offset, out_frames); + dmix_up_sem(dmix); + ret = snd_pcm_dmix_appl_forward(pcm); + if (ret < 0) + { + goto end; + } + + /* check dump raw pcm data per 50 periods */ + + if (dmix->appl_ptr % 50 == 0) + { + snd_pcm_dmix_raw_pcm_dump(pcm); + } + + if (pcm->dump_fd >= 0) + { + write(pcm->dump_fd, in_data, out_frames * dmix->frame_bytes); + } + + xfer = in_frames; + if (pcm->state == SND_PCM_STATE_PREPARED) + { + written = snd_pcm_dmix_written(pcm); + if (pcm->running) + { + pcm->state = SND_PCM_STATE_RUNNING; + } + else if (written >= pcm->start_threshold) + { + ret = snd_pcm_start(pcm); + if (ret < 0) + { + goto end; + } + } + } + +end: + SND_DEBUG("leave, xfer:%ld, ret:%ld", xfer, ret); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : ret; +} + +static snd_pcm_sframes_t snd_pcm_dmix_writei(FAR snd_pcm_t *pcm, + const void *buffer, + snd_pcm_uframes_t size) +{ + FAR snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_uframes_t xfer = 0; + snd_pcm_sframes_t ret = 0; + snd_pcm_state_t state = snd_pcm_state(pcm); + int offset_src; + int offset_dst; + FAR const uint8_t *in_data = buffer; + snd_pcm_sframes_t avail; + snd_pcm_sframes_t transfer; + + SND_DEBUG("enter, buffer:%p, size:%ld", buffer, size); + + if (size == 0) + { + return 0; + } + + switch (state) + { + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_PAUSED: + break; + case SND_PCM_STATE_XRUN: + return -EPIPE; + case SND_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case SND_PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EBADFD; + } + + while (size > 0) + { + avail = snd_pcm_avail_update(pcm); + SND_DEBUG("snd_pcm_avail_update ret:%ld", avail); + if (avail < 0) + { + ret = avail; + goto end; + } + + if (avail == 0) + { + if (pcm->mode & SND_PCM_NONBLOCK) + { + ret = -EAGAIN; + goto end; + } + + ret = snd_pcm_wait(pcm, -1); + if (ret < 0) + { + break; + } + + continue; + } + + if (avail < dmix->period_frames) + { + continue; + } + + offset_src = snd_pcm_frames_to_bytes(pcm, xfer); + if (size < pcm->period_frames || + (pcm->last_buffer.nframes > 0 && + pcm->last_buffer.nframes < pcm->last_buffer.nmaxframes)) + { + transfer = MIN(size, pcm->last_buffer.nmaxframes - + pcm->last_buffer.nframes); + offset_dst = + snd_pcm_frames_to_bytes(pcm, pcm->last_buffer.nframes); + SND_DEBUG("[size:%ld, nframes:%ld][oft_src:%d oft_dst:%d, " + "len:%ld]", + size, pcm->last_buffer.nframes, offset_src, offset_dst, + transfer * pcm->frame_bytes); + memcpy(pcm->last_buffer.data + offset_dst, in_data + offset_src, + transfer * pcm->frame_bytes); + pcm->last_buffer.nframes += transfer; + size -= transfer; + xfer += transfer; + + if (pcm->last_buffer.nframes < pcm->last_buffer.nmaxframes) + { + goto end; + } + + transfer = snd_pcm_dmix_write_period(pcm, pcm->last_buffer.data); + if (transfer < 0) + { + ret = transfer; + goto end; + } + + pcm->last_buffer.nframes -= transfer; + continue; + } + + offset_src = snd_pcm_frames_to_bytes(pcm, xfer); + transfer = snd_pcm_dmix_write_period(pcm, in_data + offset_src); + if (transfer < 0) + { + ret = transfer; + goto end; + } + + size -= transfer; + xfer += transfer; + } + +end: + SND_DEBUG("leave, xfer:%ld, ret:%ld", xfer, ret); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : ret; +} + +static int snd_pcm_dmix_poll_descriptors(FAR snd_pcm_t *pcm, + FAR struct pollfd *pfds, + unsigned int space) +{ + pfds->fd = pcm->fd; + pfds->events = POLLOUT; + + return 1; +} + +static snd_pcm_ops_t snd_pcm_dmix_ops = +{ + .state = NULL, + .delay = snd_pcm_dmix_delay, + .prepare = snd_pcm_dmix_prepare, + .reset = snd_pcm_dmix_reset, + .start = NULL, + .dump = snd_pcm_dmix_dump, + .drop = snd_pcm_dmix_drop, + .drain = snd_pcm_dmix_drain, + .close = snd_pcm_dmix_close, + .pause = NULL, + .resume = snd_pcm_dmix_resume, + .avail_update = snd_pcm_dmix_avail_update, + .writei = snd_pcm_dmix_writei, + .poll_descriptors = snd_pcm_dmix_poll_descriptors, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int snd_pcm_dmix_open(FAR snd_pcm_t **pcmp, FAR const char *name, + snd_pcm_stream_t stream, int mode) +{ + FAR snd_pcm_t *pcm = NULL; + FAR snd_pcm_dmix_t *dmix = NULL; + int ret; + + if (!pcmp || !name) + { + return -EINVAL; + } + + if (stream != SND_PCM_STREAM_PLAYBACK) + { + SND_ERR("The dmix plugin supports only playback stream"); + return -EINVAL; + } + + dmix = calloc(1, sizeof(snd_pcm_dmix_t)); + if (!dmix) + { + ret = -ENOMEM; + goto open_err; + } + + dmix->shmptr = MAP_FAILED; + dmix->hw_ptr = MAP_FAILED; + dmix->mixer.mix_buffer = MAP_FAILED; + snprintf(dmix->ipc_key, sizeof(dmix->ipc_key), "/%s", name); + + ret = snd_pcm_dmix_semaphore_open(dmix); + if (ret < 0) + { + SND_ERR("unable to create IPC semaphore"); + goto open_err; + } + + ret = snd_pcm_new(&pcm, SND_PCM_TYPE_DMIX, name, stream, mode); + if (ret < 0) + { + goto open_err; + } + + pcm->ops = &snd_pcm_dmix_ops; + pcm->private_data = dmix; + pcm->state = SND_PCM_STATE_OPEN; + + *pcmp = pcm; + return 0; + +open_err: + if (dmix) + { + if (dmix->semid) + { + snd_pcm_dmix_semaphore_close(dmix); + } + + free(dmix); + } + + if (pcm) + { + snd_pcm_free(pcm); + } + + SND_ERR("fail ret:%d", ret); + return ret; +} diff --git a/audioutils/alsa-lib/pcm_dmix.h b/audioutils/alsa-lib/pcm_dmix.h new file mode 100644 index 00000000000..97122c49de5 --- /dev/null +++ b/audioutils/alsa-lib/pcm_dmix.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_dmix.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ALSA_PCM_DMIX_H +#define __ALSA_PCM_DMIX_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "bits_convert.h" +#include "channels_map.h" +#include "pcm_local.h" + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef struct snd_pcm_mmap_status +{ + volatile unsigned long read_head; + volatile unsigned long read_tail; +} snd_pcm_mmap_status_t; + +typedef struct +{ + unsigned int refs; + snd_pcm_format_t format; + unsigned int channels; + unsigned int sample_rate; + snd_pcm_uframes_t period_frames; + snd_pcm_uframes_t buffer_frames; +} snd_pcm_dmix_share_t; + +typedef struct snd_pcm_dmix +{ + char ipc_key[16]; /* IPC key for semaphore and memory */ + FAR sem_t *semid; /* IPC global semaphore identification */ + FAR snd_pcm_dmix_share_t *shmptr; /* Pointer to shared memory area */ + + unsigned long appl_ptr; + unsigned long appl_reset; + FAR snd_pcm_mmap_status_t *hw_ptr; + + snd_pcm_format_t format; + unsigned int channels; + unsigned int sample_rate; + + int sample_bits; + int frame_bytes; + + int periods; + int period_time; + int period_bytes; + int buffer_bytes; + + snd_pcm_uframes_t period_frames; + snd_pcm_uframes_t buffer_frames; + + FAR struct bitsconv_data *bc; + FAR struct bitsconv_data *bc_s16; + FAR struct chmap_data *cm; + FAR SpeexResamplerState *resampler; + FAR uint8_t *resmp_out; + + struct + { + FAR uint8_t *mix_buffer; + FAR mix_areas_16_t *mix_areas_16; + FAR mix_areas_32_t *mix_areas_32; + FAR mix_areas_24_t *mix_areas_24; + FAR mix_areas_u8_t *mix_areas_u8; + } mixer; +} snd_pcm_dmix_t; + +#endif /* __ALSA_PCM_DMIX_H */ \ No newline at end of file diff --git a/audioutils/alsa-lib/pcm_dmix_generic.c b/audioutils/alsa-lib/pcm_dmix_generic.c new file mode 100644 index 00000000000..2d8cffb3bc8 --- /dev/null +++ b/audioutils/alsa-lib/pcm_dmix_generic.c @@ -0,0 +1,286 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_dmix_generic.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "pcm_dmix_generic.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void generic_mix_areas_16_native(unsigned int size, + FAR volatile signed short *dst, + FAR signed short *src, + size_t dst_step, size_t src_step) +{ + register signed int sample; + + for (; ; ) + { + if (!*dst) + { + *dst = *src; + } + else + { + sample = *src; + sample += *dst; + if (sample > 0x7fff) + { + sample = 0x7fff; + } + else if (sample < -0x8000) + { + sample = -0x8000; + } + + *dst = sample; + } + + if (!--size) + { + return; + } + + src = (signed short *)((char *)src + src_step); + dst = (signed short *)((char *)dst + dst_step); + } +} + +static void generic_mix_areas_32_native(unsigned int size, + FAR volatile signed int *dst, + FAR signed int *src, size_t dst_step, + size_t src_step) +{ + register signed int sample; + + for (; ; ) + { + if (!*dst) + { + *dst = *src; + } + else + { + sample = (*src >> 8); + sample += *dst >> 8; + if (sample > 0x7fffff) + { + sample = 0x7fffffff; + } + else if (sample < -0x800000) + { + sample = -0x80000000; + } + else + { + sample *= 256; + } + + *dst = sample; + } + + if (!--size) + { + return; + } + + src = (signed int *)((char *)src + src_step); + dst = (signed int *)((char *)dst + dst_step); + } +} + +static void generic_mix_areas_16_swap(unsigned int size, + FAR volatile signed short *dst, + FAR signed short *src, size_t dst_step, + size_t src_step) +{ + register signed int sample; + + for (; ; ) + { + if (!*dst) + { + *dst = *src; + } + else + { + sample = (signed short)bswap_16(*src); + sample += *dst; + if (sample > 0x7fff) + { + sample = 0x7fff; + } + else if (sample < -0x8000) + { + sample = -0x8000; + } + + *dst = (signed short)bswap_16((signed short)sample); + } + + if (!--size) + { + return; + } + + src = (signed short *)((char *)src + src_step); + dst = (signed short *)((char *)dst + dst_step); + } +} + +static void generic_mix_areas_32_swap(unsigned int size, + FAR volatile signed int *dst, + FAR signed int *src, size_t dst_step, + size_t src_step) +{ + register signed int sample; + + for (; ; ) + { + if (!*dst) + { + *dst = *src; + } + else + { + sample = (bswap_32(*src) >> 8); + sample += *dst >> 8; + if (sample > 0x7fffff) + { + sample = 0x7fffffff; + } + else if (sample < -0x800000) + { + sample = -0x80000000; + } + else + { + sample *= 256; + } + + *dst = bswap_32(sample); + } + + if (!--size) + { + return; + } + + src = (signed int *)((char *)src + src_step); + dst = (signed int *)((char *)dst + dst_step); + } +} + +/* always little endian */ + +static void generic_mix_areas_24(unsigned int size, + FAR volatile unsigned char *dst, + FAR unsigned char *src, size_t dst_step, + size_t src_step) +{ + register signed int sample; + + for (; ; ) + { + sample = (src[0] | (src[1] << 8) | (((signed char *)src)[2] << 16)); + if (dst[0] | dst[1] | dst[2]) + { + sample += *dst; + if (sample > 0x7fffff) + { + sample = 0x7fffff; + } + else if (sample < -0x800000) + { + sample = -0x800000; + } + } + + dst[0] = sample; + dst[1] = sample >> 8; + dst[2] = sample >> 16; + if (!--size) + { + return; + } + + dst += dst_step; + src += src_step; + } +} + +static void generic_mix_areas_u8(unsigned int size, + FAR volatile unsigned char *dst, + FAR unsigned char *src, size_t dst_step, + size_t src_step) +{ + for (; ; ) + { + register int sample = (*src - 0x80); + if (*dst != 0x80) + { + sample += *dst; + if (sample > 0x7f) + { + sample = 0x7f; + } + else if (sample < -0x80) + { + sample = -0x80; + } + } + + *dst = sample + 0x80; + if (!--size) + { + return; + } + + dst += dst_step; + src += src_step; + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void generic_mixer_init(FAR snd_pcm_dmix_t *dmix) +{ + if (snd_pcm_format_cpu_endian(dmix->format)) + { + dmix->mixer.mix_areas_16 = generic_mix_areas_16_native; + dmix->mixer.mix_areas_32 = generic_mix_areas_32_native; + } + else + { + dmix->mixer.mix_areas_16 = generic_mix_areas_16_swap; + dmix->mixer.mix_areas_32 = generic_mix_areas_32_swap; + } + + dmix->mixer.mix_areas_24 = generic_mix_areas_24; + dmix->mixer.mix_areas_u8 = generic_mix_areas_u8; +} diff --git a/audioutils/alsa-lib/pcm_dmix_generic.h b/audioutils/alsa-lib/pcm_dmix_generic.h new file mode 100644 index 00000000000..1e09d85d812 --- /dev/null +++ b/audioutils/alsa-lib/pcm_dmix_generic.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_dmix_generic.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ALSA_PCM_DMIX_GENERIC_H +#define __ALSA_PCM_DMIX_GENERIC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "pcm_dmix.h" + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +void generic_mixer_init(FAR snd_pcm_dmix_t *dmix); + +#endif /* __ALSA_PCM_DMIX_GENERIC_H */ \ No newline at end of file diff --git a/audioutils/alsa-lib/pcm_hw.c b/audioutils/alsa-lib/pcm_hw.c new file mode 100644 index 00000000000..0509c70ce75 --- /dev/null +++ b/audioutils/alsa-lib/pcm_hw.c @@ -0,0 +1,535 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_hw.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include "pcm_local.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int snd_pcm_hw_poll_available(FAR snd_pcm_t *pcm) +{ + bool nonblock = pcm->mode & SND_PCM_NONBLOCK; + int old = dq_count(&pcm->bufferq); + struct audio_buf_desc_s buf_desc; + int now; + + SND_DEBUG("enter"); + + while (1) + { + FAR struct ap_buffer_s *buffer; + struct audio_msg_s msg; + struct mq_attr stat; + int ret; + + ret = mq_getattr(pcm->mq, &stat); + if (ret < 0) + { + return -errno; + } + + if (nonblock && !stat.mq_curmsgs) + { + break; + } + + ret = mq_receive(pcm->mq, (FAR char *)&msg, sizeof(msg), NULL); + if (ret < 0) + { + return -errno; + } + + if (msg.msg_id == AUDIO_MSG_DEQUEUE) + { + if (pcm->state == SND_PCM_STATE_DRAINING) + { + buf_desc.u.buffer = msg.u.ptr; + ioctl(pcm->fd, AUDIOIOC_FREEBUFFER, &buf_desc); + } + else + { + buffer = msg.u.ptr; + buffer->curbyte = 0; + dq_addlast(&buffer->dq_entry, &pcm->bufferq); + } + } + else if (msg.msg_id == AUDIO_MSG_COMPLETE) + { + SND_INFO("%s complete\n", pcm->name); + pcm->state = SND_PCM_STATE_DISCONNECTED; + return 0; + } + + nonblock = true; + } + + now = dq_count(&pcm->bufferq); + if (pcm->periods > 1 && now == pcm->periods && now > old) + { + SND_WARN("audio %s, xrun occurs!\n", pcm->name); + + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + { + pcm->state = SND_PCM_STATE_XRUN; + return -EPIPE; + } + + while (!dq_empty(&pcm->bufferq)) + { + buf_desc.u.buffer = + (FAR struct ap_buffer_s *)dq_remfirst(&pcm->bufferq); + ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, &buf_desc); + } + } + + return now; +} + +static int snd_pcm_hw_peek_buffer(FAR snd_pcm_t *pcm, + FAR struct ap_buffer_s **buffer) +{ + int ret; + + *buffer = (FAR struct ap_buffer_s *)dq_peek(&pcm->bufferq); + if (*buffer) + { + return 0; + } + + ret = snd_pcm_hw_poll_available(pcm); + if (ret < 0) + { + return ret; + } + + *buffer = (FAR struct ap_buffer_s *)dq_peek(&pcm->bufferq); + if (!*buffer) + { + return -EAGAIN; + } + + return 0; +} + +static int snd_pcm_hw_enqueue_buffer(FAR snd_pcm_t *pcm, + FAR struct ap_buffer_s *buffer) +{ + struct audio_buf_desc_s desc; + + SND_DEBUG("enter"); + + if (buffer->curbyte != buffer->nmaxbytes) + { + memset(buffer->samp + buffer->curbyte, 0, + buffer->nmaxbytes - buffer->curbyte); + } + + buffer->nbytes = buffer->curbyte; + buffer->curbyte = 0; + desc.u.buffer = buffer; + return ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, &desc); +} + +static int snd_pcm_hw_delay(FAR snd_pcm_t *pcm, + FAR snd_pcm_sframes_t *delayp) +{ + int ret; + *delayp = 0; + + if (pcm->stream != SND_PCM_STREAM_PLAYBACK) + { + return 0; + } + + if (pcm->state != SND_PCM_STATE_RUNNING) + { + return 0; + } + + ret = ioctl(pcm->fd, AUDIOIOC_GETLATENCY, delayp); + + SND_INFO("get delay:%ld", *delayp); + + return ret; +} + +static int snd_pcm_hw_prepare(FAR snd_pcm_t *pcm) +{ + struct audio_caps_desc_s caps_desc; + struct audio_buf_desc_s buf_desc; + struct ap_buffer_info_s buf_info; + int ret; + int i; + + SND_DEBUG("enter, pcm->state:%d", pcm->state); + + if (pcm->state == SND_PCM_STATE_XRUN) + { + pcm->state = SND_PCM_STATE_PREPARED; + return 0; + } + + if (pcm->state != SND_PCM_STATE_SETUP) + { + return -EPERM; + } + + pcm->sample_bits = snd_pcm_get_sample_bits(pcm->format); + pcm->frame_bytes = pcm->sample_bits / 8 * pcm->channels; + + memset(&caps_desc, 0, sizeof(caps_desc)); + caps_desc.caps.ac_len = sizeof(struct audio_caps_s); + caps_desc.caps.ac_type = pcm->stream == SND_PCM_STREAM_PLAYBACK + ? AUDIO_TYPE_OUTPUT + : AUDIO_TYPE_INPUT; + caps_desc.caps.ac_channels = pcm->channels; + caps_desc.caps.ac_chmap = 0; + caps_desc.caps.ac_controls.hw[0] = pcm->sample_rate; + caps_desc.caps.ac_controls.b[3] = pcm->sample_rate >> 16; + caps_desc.caps.ac_controls.b[2] = pcm->sample_bits; + caps_desc.caps.ac_subtype = AUDIO_FMT_PCM; + + ret = ioctl(pcm->fd, AUDIOIOC_CONFIGURE, &caps_desc); + if (ret < 0) + { + return ret; + } + + SND_INFO("configure. ch:%d, rate:%d", pcm->channels, pcm->sample_rate); + + pcm->periods = pcm->periods ? pcm->periods : SND_PCM_DEFAULT_PERIODS; + + if (pcm->period_frames) + { + pcm->period_bytes = pcm->period_frames * pcm->frame_bytes; + } + else + { + pcm->period_time = + pcm->period_time ? pcm->period_time : SND_PCM_DEFAULT_PERIOD_TIME; + pcm->period_bytes = pcm->sample_rate / 1000 * pcm->period_time / 1000 * + pcm->frame_bytes; + } + + buf_info.nbuffers = pcm->periods; + buf_info.buffer_size = pcm->period_bytes; + ioctl(pcm->fd, AUDIOIOC_SETBUFFERINFO, &buf_info); + + ret = ioctl(pcm->fd, AUDIOIOC_GETBUFFERINFO, &buf_info); + if (ret >= 0) + { + pcm->periods = buf_info.nbuffers; + pcm->period_bytes = buf_info.buffer_size; + } + + pcm->buffer_bytes = pcm->period_bytes * pcm->periods; + pcm->period_frames = pcm->period_bytes / pcm->frame_bytes; + pcm->buffer_frames = pcm->period_frames * pcm->periods; + pcm->period_time = pcm->period_frames * 1000 * 1000 / pcm->sample_rate; + pcm->start_threshold = pcm->buffer_frames; + + SND_INFO("set buffer info. n:%d, size:%d", pcm->periods, + pcm->period_bytes); + + dq_init(&pcm->bufferq); + + for (i = 0; i < pcm->periods; i++) + { + FAR struct ap_buffer_s *buffer; + + buf_desc.numbytes = pcm->period_bytes; + buf_desc.u.pbuffer = &buffer; + ret = ioctl(pcm->fd, AUDIOIOC_ALLOCBUFFER, &buf_desc); + if (ret < 0) + { + return ret; + } + + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + { + dq_addlast(&buffer->dq_entry, &pcm->bufferq); + } + else + { + buffer->nbytes = buffer->nmaxbytes; + buf_desc.u.buffer = buffer; + ret = ioctl(pcm->fd, AUDIOIOC_ENQUEUEBUFFER, &buf_desc); + if (ret < 0) + { + return ret; + } + } + } + + pcm->state = SND_PCM_STATE_PREPARED; + + return 0; +} + +static int snd_pcm_hw_reset(FAR snd_pcm_t *pcm) +{ + return 0; +} + +static int snd_pcm_hw_drop(FAR snd_pcm_t *pcm) +{ + return 0; +} + +static int snd_pcm_hw_drain(FAR snd_pcm_t *pcm) +{ + return 0; +} + +static int snd_pcm_hw_close(FAR snd_pcm_t *pcm) +{ + struct audio_buf_desc_s buf_desc; + + SND_DEBUG("enter"); + + if (pcm->running) + { + ioctl(pcm->fd, AUDIOIOC_STOP, 0); + pcm->running = false; + pcm->state = SND_PCM_STATE_DRAINING; + } + + while (!dq_empty(&pcm->bufferq)) + { + buf_desc.u.buffer = + (FAR struct ap_buffer_s *)dq_remfirst(&pcm->bufferq); + ioctl(pcm->fd, AUDIOIOC_FREEBUFFER, &buf_desc); + } + + while (pcm->state == SND_PCM_STATE_DRAINING) + { + snd_pcm_hw_poll_available(pcm); + } + + snd_pcm_deinit(pcm); + + return 0; +} + +static int snd_pcm_hw_resume(FAR snd_pcm_t *pcm) +{ + if (pcm->state == SND_PCM_STATE_XRUN) + { + pcm->state = SND_PCM_STATE_PREPARED; + } + + return 0; +} + +static snd_pcm_sframes_t snd_pcm_hw_avail_update(FAR snd_pcm_t *pcm) +{ + snd_pcm_sframes_t ret; + + ret = snd_pcm_hw_poll_available(pcm); + + if (ret > 0) + { + ret *= pcm->period_frames; + } + + return ret; +} + +static snd_pcm_sframes_t snd_pcm_hw_writei(FAR snd_pcm_t *pcm, + FAR const void *buffer, + snd_pcm_uframes_t size) +{ + int left = snd_pcm_frames_to_bytes(pcm, size); + FAR const uint8_t *data = (FAR uint8_t *)buffer; + snd_pcm_state_t state = snd_pcm_state(pcm); + FAR struct ap_buffer_s *apb; + snd_pcm_uframes_t written; + snd_pcm_uframes_t xfer; + int ret = 0; + int len; + + SND_DEBUG("enter. size:%ld", size); + + if (left == 0) + { + return 0; + } + + switch (state) + { + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_PAUSED: + break; + case SND_PCM_STATE_XRUN: + return -EPIPE; + case SND_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case SND_PCM_STATE_DISCONNECTED: + return -ENODEV; + default: + return -EBADFD; + } + + while (left > 0) + { + ret = snd_pcm_hw_peek_buffer(pcm, &apb); + if (ret == -EAGAIN) + { + if (pcm->mode & SND_PCM_NONBLOCK) + { + goto end; + } + + ret = snd_pcm_wait(pcm, -1); + if (ret < 0) + { + break; + } + + continue; + } + else if (ret < 0) + { + goto end; + } + + len = MIN(apb->nmaxbytes - apb->curbyte, left); + snd_pcm_softvol_scalar(pcm, (apb->samp + apb->curbyte), data, + snd_pcm_bytes_to_frames(pcm, len) * + pcm->channels); + apb->curbyte += len; + if (apb->curbyte == apb->nmaxbytes) + { + dq_remfirst(&pcm->bufferq); + + ret = snd_pcm_hw_enqueue_buffer(pcm, apb); + if (ret < 0) + { + break; + } + + if (pcm->state == SND_PCM_STATE_PREPARED) + { + written = (pcm->periods - dq_count(&pcm->bufferq)) * + pcm->period_frames; + if (pcm->running) + { + pcm->state = SND_PCM_STATE_RUNNING; + } + else if (written >= pcm->start_threshold) + { + ret = snd_pcm_start(pcm); + if (ret < 0) + { + break; + } + } + } + } + + data += len; + left -= len; + } + +end: + xfer = size - snd_pcm_bytes_to_frames(pcm, left); + SND_DEBUG("leave. xfer:%ld, ret:%d", xfer, ret); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : ret; +} + +static int snd_pcm_hw_poll_descriptors(FAR snd_pcm_t *pcm, + FAR struct pollfd *pfds, + unsigned int space) +{ + pfds->fd = pcm->mq; + pfds->events = POLLIN; + + return 1; +} + +static snd_pcm_ops_t snd_pcm_hw_ops = +{ + .state = NULL, + .delay = snd_pcm_hw_delay, + .prepare = snd_pcm_hw_prepare, + .reset = snd_pcm_hw_reset, + .start = NULL, + .dump = NULL, + .drop = snd_pcm_hw_drop, + .drain = snd_pcm_hw_drain, + .close = snd_pcm_hw_close, + .pause = NULL, + .resume = snd_pcm_hw_resume, + .avail_update = snd_pcm_hw_avail_update, + .writei = snd_pcm_hw_writei, + .poll_descriptors = snd_pcm_hw_poll_descriptors, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int snd_pcm_hw_open(FAR snd_pcm_t **pcmp, FAR const char *name, + snd_pcm_stream_t stream, int mode) +{ + int ret; + snd_pcm_t *pcm = NULL; + + if (!pcmp || !name) + { + return -EINVAL; + } + + if (stream < SND_PCM_STREAM_PLAYBACK || stream > SND_PCM_STREAM_LAST) + { + return -EINVAL; + } + + ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, stream, mode); + if (ret < 0) + { + return ret; + } + + ret = snd_pcm_init(pcm); + if (ret < 0) + { + snd_pcm_free(pcm); + return ret; + } + + pcm->ops = &snd_pcm_hw_ops; + pcm->private_data = NULL; + pcm->state = SND_PCM_STATE_OPEN; + + *pcmp = pcm; + return ret; +} diff --git a/audioutils/alsa-lib/pcm_local.h b/audioutils/alsa-lib/pcm_local.h new file mode 100644 index 00000000000..414c412a107 --- /dev/null +++ b/audioutils/alsa-lib/pcm_local.h @@ -0,0 +1,184 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_local.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +#ifndef __ALSA_PCM_LOCAL_H +#define __ALSA_PCM_LOCAL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Prototypes + ****************************************************************************/ + +#define SND_PCM_DEFAULT_PERIODS 4 +#define SND_PCM_DEFAULT_PERIOD_TIME (20 * 1000) + +#define _log(level, fmt, args...) \ + syslog(level, "[SND][%s:%d] " fmt, __func__, __LINE__, ##args) + +#define _none(fmt, args...) \ + do { if (0) syslog(LOG_ERR, fmt, ##args); } while (0) + +#if defined(CONFIG_AUDIOUTILS_ALSA_LIB_LOG_DEBUG) +#define SND_DEBUG(fmt, args...) _log(LOG_DEBUG, fmt, ##args) +#define SND_INFO(fmt, args...) _log(LOG_INFO, fmt, ##args) +#define SND_WARN(fmt, args...) _log(LOG_WARNING, fmt, ##args) +#define SND_ERR(fmt, args...) _log(LOG_ERR, fmt, ##args) +#elif defined(CONFIG_AUDIOUTILS_ALSA_LIB_LOG_INFO) +#define SND_DEBUG(fmt, args...) _none(fmt, ##args) +#define SND_INFO(fmt, args...) _log(LOG_INFO, fmt, ##args) +#define SND_WARN(fmt, args...) _log(LOG_WARNING, fmt, ##args) +#define SND_ERR(fmt, args...) _log(LOG_ERR, fmt, ##args) +#elif defined(CONFIG_AUDIOUTILS_ALSA_LIB_LOG_WARN) +#define SND_DEBUG(fmt, args...) _none(fmt, ##args) +#define SND_INFO(fmt, args...) _none(fmt, ##args) +#define SND_WARN(fmt, args...) _log(LOG_WARNING, fmt, ##args) +#define SND_ERR(fmt, args...) _log(LOG_ERR, fmt, ##args) +#elif defined(CONFIG_AUDIOUTILS_ALSA_LIB_LOG_ERR) +#define SND_DEBUG(fmt, args...) _none(fmt, ##args) +#define SND_INFO(fmt, args...) _none(fmt, ##args) +#define SND_WARN(fmt, args...) _none(fmt, ##args) +#define SND_ERR(fmt, args...) _log(LOG_ERR, fmt, ##args) +#else +#define SND_DEBUG(fmt, args...) _none(fmt, ##args) +#define SND_INFO(fmt, args...) _none(fmt, ##args) +#define SND_WARN(fmt, args...) _none(fmt, ##args) +#define SND_ERR(fmt, args...) _none(fmt, ##args) +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef void(mix_areas_t)(unsigned int size, volatile void *dst, void *src, + size_t dst_step, size_t src_step); + +typedef void(mix_areas_16_t)(unsigned int size, volatile signed short *dst, + signed short *src, size_t dst_step, + size_t src_step); + +typedef void(mix_areas_32_t)(unsigned int size, volatile signed int *dst, + signed int *src, size_t dst_step, + size_t src_step); + +typedef void(mix_areas_24_t)(unsigned int size, volatile unsigned char *dst, + unsigned char *src, size_t dst_step, + size_t src_step); + +typedef void(mix_areas_u8_t)(unsigned int size, volatile unsigned char *dst, + unsigned char *src, size_t dst_step, + size_t src_step); + +typedef struct +{ + snd_pcm_state_t (*state)(FAR snd_pcm_t *pcm); + int (*delay)(FAR snd_pcm_t *pcm, snd_pcm_sframes_t *delayp); + int (*prepare)(FAR snd_pcm_t *pcm); + int (*reset)(FAR snd_pcm_t *pcm); + int (*start)(FAR snd_pcm_t *pcm); + void (*dump)(snd_pcm_t *pcm); + int (*drop)(FAR snd_pcm_t *pcm); + int (*drain)(FAR snd_pcm_t *pcm); + int (*close)(FAR snd_pcm_t *pcm); + int (*pause)(FAR snd_pcm_t *pcm, int enable); + int (*resume)(FAR snd_pcm_t *pcm); + snd_pcm_sframes_t (*avail_update)(snd_pcm_t *pcm); + snd_pcm_sframes_t (*writei)(FAR snd_pcm_t *pcm, const void *buffer, + snd_pcm_uframes_t size); + int (*poll_descriptors)(snd_pcm_t *pcm, struct pollfd *pfds, + unsigned int space); +} snd_pcm_ops_t; + +typedef struct +{ + snd_pcm_uframes_t nmaxframes; /* The maximum number of bytes */ + snd_pcm_uframes_t nframes; /* The number of bytes used */ + FAR uint8_t *data; /* Offset of the first sample */ +} pcm_buffer_t; + +struct snd_pcm_s +{ + int fd; /* File descriptor of active device */ + mqd_t mq; /* Message queue for the playthread */ + char mqname[32]; /* Name of our message queue */ + int dump_fd; /* File descriptor for dump raw pcm data */ + int vis_fd; /* File descriptor for visualizer */ + + int setup; + char *name; + int mode; + snd_pcm_type_t type; + snd_pcm_access_t access; + snd_pcm_stream_t stream; + snd_pcm_uframes_t start_threshold; /* auto start with frames */ + + float volume; /* Volume percentage in [0-1] */ + snd_pcm_format_t format; + unsigned int channels; + unsigned int sample_rate; + + snd_pcm_state_t state; + bool running; + + int sample_bits; + int frame_bytes; + int periods; + int period_time; /* us */ + int period_bytes; + int buffer_bytes; + + snd_pcm_uframes_t period_frames; + snd_pcm_uframes_t buffer_frames; + + dq_queue_t bufferq; + pcm_buffer_t last_buffer; + + snd_pcm_ops_t *ops; + snd_pcm_t *ops_arg; + void *private_data; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int snd_pcm_new(FAR snd_pcm_t **pcmp, snd_pcm_type_t type, + FAR const char *name, snd_pcm_stream_t stream, int mode); +int snd_pcm_free(FAR snd_pcm_t *pcm); +int snd_pcm_hw_open(FAR snd_pcm_t **pcmp, FAR const char *name, + snd_pcm_stream_t stream, int mode); +int snd_pcm_dmix_open(FAR snd_pcm_t **pcmp, FAR const char *name, + snd_pcm_stream_t stream, int mode); +int snd_pcm_init(FAR snd_pcm_t *pcm); +void snd_pcm_deinit(FAR snd_pcm_t *pcm); + +void snd_pcm_softvol_scalar(FAR snd_pcm_t *pcm, FAR uint8_t *dest, + FAR const uint8_t *src, int samples); + +#endif /* __ALSA_PCM_LOCAL_H */ \ No newline at end of file diff --git a/audioutils/alsa-lib/pcm_params.c b/audioutils/alsa-lib/pcm_params.c new file mode 100644 index 00000000000..9ce017c6678 --- /dev/null +++ b/audioutils/alsa-lib/pcm_params.c @@ -0,0 +1,426 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_params.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include "pcm_local.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline int snd_pcm_hw_is_mask(int var) +{ + return var >= SND_PCM_HW_PARAM_FIRST_MASK && + var <= SND_PCM_HW_PARAM_LAST_MASK; +} + +static inline int snd_pcm_hw_is_range(int var) +{ + return var >= SND_PCM_HW_PARAM_FIRST_RANGE && + var <= SND_PCM_HW_PARAM_LAST_RANGE; +} + +static int snd_pcm_hw_param_set(FAR snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val) +{ + FAR snd_interval_t *interval = NULL; + + assert(params); + interval = ¶ms->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL]; + if (snd_pcm_hw_is_mask(var)) + { + interval->mask = val; + } + else if (snd_pcm_hw_is_range(var)) + { + interval->range.min = val; + } + else + { + assert(0); + return -EINVAL; + } + + return 0; +} + +static int snd_pcm_hw_param_get(FAR const snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, + FAR unsigned int *val) +{ + FAR const snd_interval_t *interval; + + assert(params); + interval = ¶ms->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL]; + if (snd_pcm_hw_is_mask(var)) + { + *val = interval->mask; + } + else + { + *val = interval->range.min; + } + + return 0; +} + +#define snd_pcm_hw_param_set(params, var, val) \ + snd_pcm_hw_param_set(params, var, (unsigned int)(val)) +#define snd_pcm_hw_param_get(params, var, val) \ + snd_pcm_hw_param_get(params, var, (FAR unsigned int *)(val)) + +static int snd_pcm_hw_params_internal(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params) +{ + int period_size; + int buffer_size; + + assert(pcm && params); + + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_FORMAT, &pcm->format); + if (!pcm->format) + { + pcm->format = SND_PCM_FORMAT_S16; + } + + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_CHANNELS, &pcm->channels); + if (!pcm->channels) + { + pcm->channels = 2; + } + + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_RATE, &pcm->sample_rate); + if (!pcm->sample_rate) + { + pcm->sample_rate = 44100; + } + + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIODS, &pcm->periods); + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIOD_TIME, + &pcm->period_time); + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIOD_SIZE, &period_size); + snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &buffer_size); + if (period_size && buffer_size) + { + pcm->periods = buffer_size / period_size; + pcm->period_time = period_size * 1000 * 1000 / pcm->sample_rate; + } + + SND_INFO("format:%d, ch:%d, rate:%d, periods:%d, period_time:%d", + pcm->format, pcm->channels, pcm->sample_rate, pcm->periods, + pcm->period_time); + + pcm->state = SND_PCM_STATE_SETUP; + pcm->setup = 1; + + return 0; +} + +/**************************************************************************** + * Public HW Functions + ****************************************************************************/ + +int snd_pcm_hw_params_any(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params) +{ + return 0; +} + +int snd_pcm_hw_params_set_format(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_format_t format) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_FORMAT, format); +} + +int snd_pcm_hw_params_set_channels(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int val) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS, val); +} + +int snd_pcm_hw_params_get_channels(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *val) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_CHANNELS, val); +} + +int snd_pcm_hw_params_set_rate(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int val, int dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_RATE, val); +} + +int snd_pcm_hw_params_set_rate_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_RATE, *val); +} + +int snd_pcm_hw_params_get_rate(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_RATE, val); +} + +int snd_pcm_hw_params_set_period_time(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int us, int dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIOD_TIME, us); +} + +int snd_pcm_hw_params_set_period_time_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *us, + FAR int *dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIOD_TIME, *us); +} + +int snd_pcm_hw_params_get_period_time(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *us, FAR int *dir) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIOD_TIME, us); +} + +int snd_pcm_hw_params_set_period_size(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_uframes_t val, int dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIOD_SIZE, val); +} + +int snd_pcm_hw_params_set_period_size_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val, + FAR int *dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIOD_SIZE, *val); +} + +int snd_pcm_hw_params_get_period_size(FAR const snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val, + FAR int *dir) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIOD_SIZE, val); +} + +int snd_pcm_hw_params_set_periods(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int val, int dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIODS, val); +} + +int snd_pcm_hw_params_set_periods_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIODS, *val); +} + +int snd_pcm_hw_params_get_periods(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *val, FAR int *dir) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIODS, val); +} + +int snd_pcm_hw_params_set_buffer_size(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_uframes_t val) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_BUFFER_SIZE, val); +} + +int snd_pcm_hw_params_set_buffer_size_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_BUFFER_SIZE, *val); +} + +int snd_pcm_hw_params_get_buffer_size(FAR const snd_pcm_hw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_BUFFER_SIZE, val); +} + +int snd_pcm_hw_params_set_buffer_time(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + unsigned int us) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_BUFFER_TIME, us); +} + +int snd_pcm_hw_params_set_buffer_time_near(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + FAR unsigned int *us, + FAR int *dir) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_BUFFER_TIME, *us); +} + +int snd_pcm_hw_params_get_buffer_time(FAR const snd_pcm_hw_params_t *params, + FAR unsigned int *us, FAR int *dir) +{ + return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_BUFFER_TIME, us); +} + +int snd_pcm_hw_params_set_access(FAR snd_pcm_t *pcm, + FAR snd_pcm_hw_params_t *params, + snd_pcm_access_t access) +{ + return snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_ACCESS, access); +} + +int snd_pcm_hw_params(FAR snd_pcm_t *pcm, FAR snd_pcm_hw_params_t *params) +{ + int ret; + assert(pcm && params); + + ret = snd_pcm_hw_params_internal(pcm, params); + if (ret < 0) + { + return ret; + } + + ret = snd_pcm_prepare(pcm); + return ret; +} + +/**************************************************************************** + * Public SW Functions + ****************************************************************************/ + +int snd_pcm_sw_params_current(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params) +{ + assert(pcm && params); + + memset(params, 0, sizeof(snd_pcm_sw_params_t)); + params->avail_min = pcm->period_frames; + params->start_threshold = pcm->start_threshold; + + return 0; +} + +int snd_pcm_sw_params_set_start_threshold(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val) +{ + assert(pcm && params); + + params->start_threshold = val; + return 0; +} + +int snd_pcm_sw_params_get_start_threshold(FAR snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + assert(params); + + *val = params->start_threshold; + return 0; +} + +int snd_pcm_sw_params_set_stop_threshold(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val) +{ + assert(pcm && params); + + params->stop_threshold = val; + return 0; +} + +int snd_pcm_sw_params_get_stop_threshold(FAR snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + assert(params); + + *val = params->stop_threshold; + return 0; +} + +int snd_pcm_sw_params_set_silence_size(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val) +{ + assert(pcm && params); + + params->silence_size = val; + return 0; +} + +int snd_pcm_sw_params_get_silence_size(FAR snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + assert(params); + + *val = params->silence_size; + return 0; +} + +int snd_pcm_sw_params_set_avail_min(FAR snd_pcm_t *pcm, + FAR snd_pcm_sw_params_t *params, + snd_pcm_uframes_t val) +{ + assert(pcm && params); + + params->avail_min = val; + return 0; +} + +int snd_pcm_sw_params_get_avail_min(FAR const snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + assert(params); + + *val = params->avail_min; + return 0; +} + +int snd_pcm_sw_params_get_boundary(FAR const snd_pcm_sw_params_t *params, + FAR snd_pcm_uframes_t *val) +{ + assert(params); + + *val = params->boundary; + return 0; +} + +int snd_pcm_sw_params(FAR snd_pcm_t *pcm, FAR snd_pcm_sw_params_t *params) +{ + assert(pcm && params); + + pcm->start_threshold = params->start_threshold; + return 0; +} diff --git a/audioutils/alsa-lib/pcm_softvol.c b/audioutils/alsa-lib/pcm_softvol.c new file mode 100644 index 00000000000..a7149685812 --- /dev/null +++ b/audioutils/alsa-lib/pcm_softvol.c @@ -0,0 +1,75 @@ +/**************************************************************************** + * apps/audioutils/alsa-lib/pcm_softvol.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "pcm_local.h" + +/**************************************************************************** + * Pre-processor Prototypes + ****************************************************************************/ + +#define PCM_SND_SCALAR(name, type) \ + static void snd_pcm_scalar_##name(FAR uint8_t *dst, \ + FAR const uint8_t *src, int nb_samples, \ + float volume) \ + { \ + FAR type *d = (FAR type *)dst; \ + FAR const type *s = (FAR type *)src; \ + int i = 0; \ + \ + for (i = 0; i < nb_samples; i++) \ + { \ + d[i] = s[i] * volume; \ + } \ + } + +PCM_SND_SCALAR(s16, int16_t) +PCM_SND_SCALAR(u16, uint16_t) +PCM_SND_SCALAR(s32, int32_t) +PCM_SND_SCALAR(u32, uint32_t) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +void snd_pcm_softvol_scalar(FAR snd_pcm_t *pcm, FAR uint8_t *dest, + FAR const uint8_t *src, int samples) +{ + switch (pcm->format) + { + case SND_PCM_FORMAT_S16: + snd_pcm_scalar_s16(dest, src, samples, pcm->volume); + break; + case SND_PCM_FORMAT_U16: + snd_pcm_scalar_u16(dest, src, samples, pcm->volume); + break; + case SND_PCM_FORMAT_S32: + snd_pcm_scalar_s32(dest, src, samples, pcm->volume); + break; + case SND_PCM_FORMAT_U32: + snd_pcm_scalar_u32(dest, src, samples, pcm->volume); + break; + default: + SND_ERR("Not support yet! format:%d", pcm->format); + } +} diff --git a/audioutils/speexdsp/.gitignore b/audioutils/speexdsp/.gitignore new file mode 100644 index 00000000000..1d117f0e98a --- /dev/null +++ b/audioutils/speexdsp/.gitignore @@ -0,0 +1 @@ +/speexdsp diff --git a/audioutils/speexdsp/CMakeLists.txt b/audioutils/speexdsp/CMakeLists.txt new file mode 100644 index 00000000000..eaf7c5310e9 --- /dev/null +++ b/audioutils/speexdsp/CMakeLists.txt @@ -0,0 +1,66 @@ +# ############################################################################## +# apps/audioutils/speexdsp/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you 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. +# +# ############################################################################## +if(CONFIG_AUDIOUTILS_SPEEXDSP) + + if(NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/speexdsp) + if(NOT SPEEXDSP_URL) + set(SPEEXDSP_URL "https://github.com/xiph/speexdsp/archive/refs/tags") + endif() + + if(NOT SPEEXDSP_VERSION) + set(SPEEXDSP_VERSION "SpeexDSP-1.2.1") + endif() + + FetchContent_Declare( + speexdsp + URL "${SPEEXDSP_URL}/${SPEEXDSP_VERSION}.zip" + SOURCE_DIR + ${CMAKE_CURRENT_LIST_DIR}/speexdsp + BINARY_DIR + ${CMAKE_BINARY_DIR}/apps/audioutils/speexdsp/speexdsp + CONFIGURE_COMMAND + "" + BUILD_COMMAND + "" + INSTALL_COMMAND + "" + TEST_COMMAND + "" + DOWNLOAD_NO_PROGRESS true + TIMEOUT 30) + + FetchContent_GetProperties(speexdsp) + + if(NOT speexdsp_POPULATED) + FetchContent_Populate(speexdsp) + endif() + endif() + + set(CSRCS speexdsp/libspeexdsp/resample.c) + add_definitions(-DOUTSIDE_SPEEX) + add_definitions(-DRANDOM_PREFIX=nuttx) + + if(CONFIG_FIXED_POINT) + add_definitions(-DFIXED_POINT) + else() + add_definitions(-DFLOATING_POINT) + endif() + +endif() diff --git a/audioutils/speexdsp/Kconfig b/audioutils/speexdsp/Kconfig new file mode 100644 index 00000000000..2511f3ef68f --- /dev/null +++ b/audioutils/speexdsp/Kconfig @@ -0,0 +1,26 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config AUDIOUTILS_SPEEXDSP + bool "Audio libspeexdsp Library" + default n + ---help--- + Enable build for various resample functions + +if AUDIOUTILS_SPEEXDSP + +choice + prompt "Resample precision" + default FIXED_POINT + +config FIXED_POINT + bool "fixed point precision" + +config FLOATING_POINT + bool "floating point precision" + +endchoice # Resample precision + +endif # AUDIOUTILS_SPEEXDSP diff --git a/audioutils/speexdsp/Make.defs b/audioutils/speexdsp/Make.defs new file mode 100644 index 00000000000..522dff13788 --- /dev/null +++ b/audioutils/speexdsp/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/audioutils/speexdsp/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ +ifneq ($(CONFIG_AUDIOUTILS_SPEEXDSP),) +CONFIGURED_APPS += $(APPDIR)/audioutils/speexdsp + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/audioutils/speexdsp/speexdsp/include/speex +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/audioutils/speexdsp/speexdsp/include/speex +endif \ No newline at end of file diff --git a/audioutils/speexdsp/Makefile b/audioutils/speexdsp/Makefile new file mode 100644 index 00000000000..336cecf368c --- /dev/null +++ b/audioutils/speexdsp/Makefile @@ -0,0 +1,58 @@ +############################################################################ +# apps/audioutils/speexdsp/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +include $(APPDIR)/Make.defs + +SPEEXDSP_URL ?= "https://github.com/xiph/speexdsp/archive/refs/tags" +SPEEXDSP_VERSION ?= SpeexDSP-1.2.1 + +SPEEXDSP_DIR := $(APPDIR)/audioutils/speexdsp +SPEEXDSP_UNPACKNAME := speexdsp +SPEEXDSP_UNPACKDIR := $(SPEEXDSP_DIR)/$(SPEEXDSP_UNPACKNAME) + +$(SPEEXDSP_UNPACKDIR): + @echo "Downloading: $(SPEEXDSP_UNPACKNAME)" + $(Q) curl -O -L $(SPEEXDSP_URL)/$(SPEEXDSP_VERSION).zip + $(Q) unzip -o $(SPEEXDSP_VERSION).zip + $(call DELFILE, $(SPEEXDSP_VERSION).zip) + $(call MOVEFILE, speexdsp-$(SPEEXDSP_VERSION), $(SPEEXDSP_UNPACKDIR)) + +CSRCS += $(SPEEXDSP_UNPACKDIR)/libspeexdsp/resample.c + +CFLAGS += -DOUTSIDE_SPEEX -DRANDOM_PREFIX=nuttx + +ifeq ($(CONFIG_FIXED_POINT),y) +CFLAGS += -DFIXED_POINT +else +CFLAGS += -DFLOATING_POINT +endif + +clean:: + $(call DELFILE, $(OBJS)) + +# Download and unpack tarball if no git repo found +ifeq ($(wildcard $(SPEEXDSP_UNPACKDIR)/.git),) +context:: $(SPEEXDSP_UNPACKDIR) + +distclean:: + $(call DELDIR, $(SPEEXDSP_UNPACKDIR)) +endif + +include $(APPDIR)/Application.mk