| /** | |
| * \file pcm/pcm_null.c | |
| * \ingroup PCM_Plugins | |
| * \brief PCM Null Plugin Interface | |
| * \author Abramo Bagnara <abramo@alsa-project.org> | |
| * \date 2000-2001 | |
| */ | |
| /* | |
| * PCM - Null plugin | |
| * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> | |
| * | |
| * | |
| * This library is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as | |
| * published by the Free Software Foundation; either version 2.1 of | |
| * the License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #include "pcm_local.h" | |
| #include "pcm_plugin.h" | |
| #include "bswap.h" | |
| #include <limits.h> | |
| #ifndef PIC | |
| /* entry for static linking */ | |
| const char *_snd_module_pcm_null = ""; | |
| #endif | |
| #ifndef DOC_HIDDEN | |
| typedef struct { | |
| snd_htimestamp_t trigger_tstamp; | |
| snd_pcm_state_t state; | |
| snd_pcm_uframes_t appl_ptr; | |
| snd_pcm_uframes_t hw_ptr; | |
| int poll_fd; | |
| snd_pcm_chmap_query_t **chmap; | |
| } snd_pcm_null_t; | |
| #endif | |
| static int snd_pcm_null_close(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| close(null->poll_fd); | |
| free(null); | |
| return 0; | |
| } | |
| static int snd_pcm_null_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_null_async(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int sig ATTRIBUTE_UNUSED, pid_t pid ATTRIBUTE_UNUSED) | |
| { | |
| return -ENOSYS; | |
| } | |
| static int snd_pcm_null_info(snd_pcm_t *pcm, snd_pcm_info_t * info) | |
| { | |
| memset(info, 0, sizeof(*info)); | |
| info->stream = pcm->stream; | |
| info->card = -1; | |
| if (pcm->name) { | |
| snd_strlcpy((char *)info->id, pcm->name, sizeof(info->id)); | |
| snd_strlcpy((char *)info->name, pcm->name, sizeof(info->name)); | |
| snd_strlcpy((char *)info->subname, pcm->name, sizeof(info->subname)); | |
| } | |
| info->subdevices_count = 1; | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_avail_update(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| if (null->state == SND_PCM_STATE_PREPARED) { | |
| /* it is required to return the correct avail count for */ | |
| /* the prepared stream, otherwise the start is not called */ | |
| return snd_pcm_mmap_avail(pcm); | |
| } | |
| return pcm->buffer_size; | |
| } | |
| static int snd_pcm_null_status(snd_pcm_t *pcm, snd_pcm_status_t * status) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| memset(status, 0, sizeof(*status)); | |
| status->state = null->state; | |
| status->trigger_tstamp = null->trigger_tstamp; | |
| status->appl_ptr = *pcm->appl.ptr; | |
| status->hw_ptr = *pcm->hw.ptr; | |
| gettimestamp(&status->tstamp, pcm->tstamp_type); | |
| status->avail = snd_pcm_null_avail_update(pcm); | |
| status->avail_max = pcm->buffer_size; | |
| return 0; | |
| } | |
| static snd_pcm_state_t snd_pcm_null_state(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| return null->state; | |
| } | |
| static int snd_pcm_null_hwsync(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_null_delay(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sframes_t *delayp) | |
| { | |
| *delayp = 0; | |
| return 0; | |
| } | |
| static int snd_pcm_null_reset(snd_pcm_t *pcm) | |
| { | |
| *pcm->appl.ptr = 0; | |
| *pcm->hw.ptr = 0; | |
| return 0; | |
| } | |
| static int snd_pcm_null_prepare(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| null->state = SND_PCM_STATE_PREPARED; | |
| return snd_pcm_null_reset(pcm); | |
| } | |
| static int snd_pcm_null_start(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| assert(null->state == SND_PCM_STATE_PREPARED); | |
| null->state = SND_PCM_STATE_RUNNING; | |
| if (pcm->stream == SND_PCM_STREAM_CAPTURE) | |
| *pcm->hw.ptr = *pcm->appl.ptr + pcm->buffer_size; | |
| else | |
| *pcm->hw.ptr = *pcm->appl.ptr; | |
| return 0; | |
| } | |
| static int snd_pcm_null_drop(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| assert(null->state != SND_PCM_STATE_OPEN); | |
| null->state = SND_PCM_STATE_SETUP; | |
| return 0; | |
| } | |
| static int snd_pcm_null_drain(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| assert(null->state != SND_PCM_STATE_OPEN); | |
| null->state = SND_PCM_STATE_SETUP; | |
| return 0; | |
| } | |
| static int snd_pcm_null_pause(snd_pcm_t *pcm, int enable) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| if (enable) { | |
| if (null->state != SND_PCM_STATE_RUNNING) | |
| return -EBADFD; | |
| null->state = SND_PCM_STATE_PAUSED; | |
| } else { | |
| if (null->state != SND_PCM_STATE_PAUSED) | |
| return -EBADFD; | |
| null->state = SND_PCM_STATE_RUNNING; | |
| } | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_rewindable(snd_pcm_t *pcm) | |
| { | |
| return pcm->buffer_size; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_forwardable(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| switch (null->state) { | |
| case SND_PCM_STATE_RUNNING: | |
| snd_pcm_mmap_hw_backward(pcm, frames); | |
| /* Fall through */ | |
| case SND_PCM_STATE_PREPARED: | |
| snd_pcm_mmap_appl_backward(pcm, frames); | |
| return frames; | |
| default: | |
| return -EBADFD; | |
| } | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| switch (null->state) { | |
| case SND_PCM_STATE_RUNNING: | |
| snd_pcm_mmap_hw_forward(pcm, frames); | |
| /* Fall through */ | |
| case SND_PCM_STATE_PREPARED: | |
| snd_pcm_mmap_appl_forward(pcm, frames); | |
| return frames; | |
| default: | |
| return -EBADFD; | |
| } | |
| } | |
| static int snd_pcm_null_resume(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_xfer_areas(snd_pcm_t *pcm, | |
| const snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED, | |
| snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, | |
| snd_pcm_uframes_t size) | |
| { | |
| snd_pcm_mmap_appl_forward(pcm, size); | |
| snd_pcm_mmap_hw_forward(pcm, size); | |
| return size; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_writei(snd_pcm_t *pcm, const void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size) | |
| { | |
| return snd_pcm_write_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_writen(snd_pcm_t *pcm, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size) | |
| { | |
| return snd_pcm_write_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_readi(snd_pcm_t *pcm, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size) | |
| { | |
| return snd_pcm_read_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_readn(snd_pcm_t *pcm, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size) | |
| { | |
| return snd_pcm_read_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_null_mmap_commit(snd_pcm_t *pcm, | |
| snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, | |
| snd_pcm_uframes_t size) | |
| { | |
| return snd_pcm_null_forward(pcm, size); | |
| } | |
| static int snd_pcm_null_hw_refine(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params) | |
| { | |
| int err; | |
| /* Do not return a period size of 0 because for example portaudio cannot | |
| * handle it. | |
| */ | |
| err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_PERIOD_SIZE, 1, | |
| 0); | |
| if (err < 0) | |
| return err; | |
| err = snd_pcm_hw_refine_soft(pcm, params); | |
| params->info = SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID | | |
| SND_PCM_INFO_RESUME | SND_PCM_INFO_PAUSE; | |
| params->fifo_size = 0; | |
| return err; | |
| } | |
| static int snd_pcm_null_hw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t * params ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_null_hw_free(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_null_sw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t * params ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static snd_pcm_chmap_query_t **snd_pcm_null_query_chmaps(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| if (null->chmap) | |
| return _snd_pcm_copy_chmap_query(null->chmap); | |
| return NULL; | |
| } | |
| static snd_pcm_chmap_t *snd_pcm_null_get_chmap(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_null_t *null = pcm->private_data; | |
| if (null->chmap) | |
| return _snd_pcm_choose_fixed_chmap(pcm, null->chmap); | |
| return NULL; | |
| } | |
| static void snd_pcm_null_dump(snd_pcm_t *pcm, snd_output_t *out) | |
| { | |
| snd_output_printf(out, "Null PCM\n"); | |
| if (pcm->setup) { | |
| snd_output_printf(out, "Its setup is:\n"); | |
| snd_pcm_dump_setup(pcm, out); | |
| } | |
| } | |
| static const snd_pcm_ops_t snd_pcm_null_ops = { | |
| .close = snd_pcm_null_close, | |
| .info = snd_pcm_null_info, | |
| .hw_refine = snd_pcm_null_hw_refine, | |
| .hw_params = snd_pcm_null_hw_params, | |
| .hw_free = snd_pcm_null_hw_free, | |
| .sw_params = snd_pcm_null_sw_params, | |
| .channel_info = snd_pcm_generic_channel_info, | |
| .dump = snd_pcm_null_dump, | |
| .nonblock = snd_pcm_null_nonblock, | |
| .async = snd_pcm_null_async, | |
| .mmap = snd_pcm_generic_mmap, | |
| .munmap = snd_pcm_generic_munmap, | |
| .query_chmaps = snd_pcm_null_query_chmaps, | |
| .get_chmap = snd_pcm_null_get_chmap, | |
| .set_chmap = NULL, | |
| }; | |
| static const snd_pcm_fast_ops_t snd_pcm_null_fast_ops = { | |
| .status = snd_pcm_null_status, | |
| .state = snd_pcm_null_state, | |
| .hwsync = snd_pcm_null_hwsync, | |
| .delay = snd_pcm_null_delay, | |
| .prepare = snd_pcm_null_prepare, | |
| .reset = snd_pcm_null_reset, | |
| .start = snd_pcm_null_start, | |
| .drop = snd_pcm_null_drop, | |
| .drain = snd_pcm_null_drain, | |
| .pause = snd_pcm_null_pause, | |
| .rewindable = snd_pcm_null_rewindable, | |
| .rewind = snd_pcm_null_rewind, | |
| .forwardable = snd_pcm_null_forwardable, | |
| .forward = snd_pcm_null_forward, | |
| .resume = snd_pcm_null_resume, | |
| .writei = snd_pcm_null_writei, | |
| .writen = snd_pcm_null_writen, | |
| .readi = snd_pcm_null_readi, | |
| .readn = snd_pcm_null_readn, | |
| .avail_update = snd_pcm_null_avail_update, | |
| .mmap_commit = snd_pcm_null_mmap_commit, | |
| .htimestamp = snd_pcm_generic_real_htimestamp, | |
| }; | |
| /** | |
| * \brief Creates a new null PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param stream Stream type | |
| * \param mode Stream mode | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int snd_pcm_null_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode) | |
| { | |
| snd_pcm_t *pcm; | |
| snd_pcm_null_t *null; | |
| int fd; | |
| int err; | |
| assert(pcmp); | |
| if (stream == SND_PCM_STREAM_PLAYBACK) { | |
| fd = open("/dev/null", O_WRONLY); | |
| if (fd < 0) { | |
| SYSERR("Cannot open /dev/null"); | |
| return -errno; | |
| } | |
| } else { | |
| fd = open("/dev/full", O_RDONLY); | |
| if (fd < 0) { | |
| SYSERR("Cannot open /dev/full"); | |
| return -errno; | |
| } | |
| } | |
| null = calloc(1, sizeof(snd_pcm_null_t)); | |
| if (!null) { | |
| close(fd); | |
| return -ENOMEM; | |
| } | |
| null->poll_fd = fd; | |
| null->state = SND_PCM_STATE_OPEN; | |
| err = snd_pcm_new(&pcm, SND_PCM_TYPE_NULL, name, stream, mode); | |
| if (err < 0) { | |
| close(fd); | |
| free(null); | |
| return err; | |
| } | |
| pcm->ops = &snd_pcm_null_ops; | |
| pcm->fast_ops = &snd_pcm_null_fast_ops; | |
| pcm->private_data = null; | |
| pcm->poll_fd = fd; | |
| pcm->poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; | |
| snd_pcm_set_hw_ptr(pcm, &null->hw_ptr, -1, 0); | |
| snd_pcm_set_appl_ptr(pcm, &null->appl_ptr, -1, 0); | |
| *pcmp = pcm; | |
| return 0; | |
| } | |
| /*! \page pcm_plugins | |
| \section pcm_plugins_null Plugin: Null | |
| This plugin discards contents of a PCM stream or creates a stream with zero | |
| samples. | |
| Note: This implementation uses devices /dev/null (playback, must be writable) | |
| and /dev/full (capture, must be readable). | |
| \code | |
| pcm.name { | |
| type null # Null PCM | |
| [chmap MAP] # Provide channel maps; MAP is a string array | |
| } | |
| \endcode | |
| \subsection pcm_plugins_null_funcref Function reference | |
| <UL> | |
| <LI>snd_pcm_null_open() | |
| <LI>_snd_pcm_null_open() | |
| </UL> | |
| */ | |
| /** | |
| * \brief Creates a new Null PCM | |
| * \param pcmp Returns created PCM handle | |
| * \param name Name of PCM | |
| * \param root Root configuration node | |
| * \param conf Configuration node with Null PCM description | |
| * \param stream Stream type | |
| * \param mode Stream mode | |
| * \retval zero on success otherwise a negative error code | |
| * \warning Using of this function might be dangerous in the sense | |
| * of compatibility reasons. The prototype might be freely | |
| * changed in future. | |
| */ | |
| int _snd_pcm_null_open(snd_pcm_t **pcmp, const char *name, | |
| snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, | |
| snd_pcm_stream_t stream, int mode) | |
| { | |
| snd_config_iterator_t i, next; | |
| snd_pcm_null_t *null; | |
| snd_pcm_chmap_query_t **chmap = NULL; | |
| int err; | |
| snd_config_for_each(i, next, conf) { | |
| snd_config_t *n = snd_config_iterator_entry(i); | |
| const char *id; | |
| if (snd_config_get_id(n, &id) < 0) | |
| continue; | |
| if (snd_pcm_conf_generic_id(id)) | |
| continue; | |
| if (strcmp(id, "chmap") == 0) { | |
| snd_pcm_free_chmaps(chmap); | |
| chmap = _snd_pcm_parse_config_chmaps(n); | |
| if (!chmap) { | |
| SNDERR("Invalid channel map for %s", id); | |
| return -EINVAL; | |
| } | |
| continue; | |
| } | |
| SNDERR("Unknown field %s", id); | |
| snd_pcm_free_chmaps(chmap); | |
| return -EINVAL; | |
| } | |
| err = snd_pcm_null_open(pcmp, name, stream, mode); | |
| if (err < 0) { | |
| snd_pcm_free_chmaps(chmap); | |
| return err; | |
| } | |
| null = (*pcmp)->private_data; | |
| null->chmap = chmap; | |
| return 0; | |
| } | |
| #ifndef DOC_HIDDEN | |
| SND_DLSYM_BUILD_VERSION(_snd_pcm_null_open, SND_PCM_DLSYM_VERSION); | |
| #endif |