| /** | |
| * \file pcm/pcm_ioplug.c | |
| * \ingroup Plugin_SDK | |
| * \brief I/O Plugin SDK | |
| * \author Takashi Iwai <tiwai@suse.de> | |
| * \date 2005 | |
| */ | |
| /* | |
| * PCM - External I/O Plugin SDK | |
| * Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de> | |
| * | |
| * | |
| * 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_ioplug.h" | |
| #include "pcm_ext_parm.h" | |
| #include "pcm_generic.h" | |
| #ifndef PIC | |
| /* entry for static linking */ | |
| const char *_snd_module_pcm_ioplug = ""; | |
| #endif | |
| #ifndef DOC_HIDDEN | |
| /* hw_params */ | |
| typedef struct snd_pcm_ioplug_priv { | |
| snd_pcm_ioplug_t *data; | |
| struct snd_ext_parm params[SND_PCM_IOPLUG_HW_PARAMS]; | |
| snd_pcm_uframes_t last_hw; | |
| snd_pcm_uframes_t avail_max; | |
| snd_htimestamp_t trigger_tstamp; | |
| } ioplug_priv_t; | |
| static int snd_pcm_ioplug_drop(snd_pcm_t *pcm); | |
| static int snd_pcm_ioplug_poll_descriptors_count(snd_pcm_t *pcm); | |
| static int snd_pcm_ioplug_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space); | |
| static int snd_pcm_ioplug_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents); | |
| /* update the hw pointer */ | |
| /* called in lock */ | |
| static void snd_pcm_ioplug_hw_ptr_update(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| snd_pcm_sframes_t hw; | |
| hw = io->data->callback->pointer(io->data); | |
| if (hw >= 0) { | |
| snd_pcm_uframes_t delta; | |
| snd_pcm_uframes_t avail; | |
| if ((snd_pcm_uframes_t)hw >= io->last_hw) | |
| delta = hw - io->last_hw; | |
| else { | |
| const snd_pcm_uframes_t wrap_point = | |
| (io->data->flags & SND_PCM_IOPLUG_FLAG_BOUNDARY_WA) ? | |
| pcm->boundary : pcm->buffer_size; | |
| delta = wrap_point + hw - io->last_hw; | |
| } | |
| snd_pcm_mmap_hw_forward(io->data->pcm, delta); | |
| /* stop the stream if all samples are drained */ | |
| if (io->data->state == SND_PCM_STATE_DRAINING) { | |
| avail = snd_pcm_mmap_avail(pcm); | |
| if (avail >= pcm->buffer_size) | |
| snd_pcm_ioplug_drop(pcm); | |
| } | |
| io->last_hw = (snd_pcm_uframes_t)hw; | |
| } else { | |
| if (io->data->state == SND_PCM_STATE_DRAINING) | |
| snd_pcm_ioplug_drop(pcm); | |
| else | |
| io->data->state = SNDRV_PCM_STATE_XRUN; | |
| } | |
| } | |
| static int snd_pcm_ioplug_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 int snd_pcm_ioplug_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info) | |
| { | |
| return snd_pcm_channel_info_shm(pcm, info, -1); | |
| } | |
| static int snd_pcm_ioplug_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->version >= 0x010001 && | |
| io->data->callback->delay) | |
| return io->data->callback->delay(io->data, delayp); | |
| else { | |
| snd_pcm_ioplug_hw_ptr_update(pcm); | |
| *delayp = snd_pcm_mmap_delay(pcm); | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_status(snd_pcm_t *pcm, snd_pcm_status_t * status) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| snd_pcm_sframes_t sd; | |
| memset(status, 0, sizeof(*status)); | |
| snd_pcm_ioplug_hw_ptr_update(pcm); | |
| status->state = io->data->state; | |
| status->trigger_tstamp = io->trigger_tstamp; | |
| gettimestamp(&status->tstamp, pcm->tstamp_type); | |
| status->avail = snd_pcm_mmap_avail(pcm); | |
| status->avail_max = io->avail_max; | |
| status->appl_ptr = *pcm->appl.ptr; | |
| status->hw_ptr = *pcm->hw.ptr; | |
| if (snd_pcm_ioplug_delay(pcm, &sd) < 0) | |
| sd = snd_pcm_mmap_delay(pcm); | |
| status->delay = sd; | |
| return 0; | |
| } | |
| static snd_pcm_state_t snd_pcm_ioplug_state(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| return io->data->state; | |
| } | |
| static int snd_pcm_ioplug_hwsync(snd_pcm_t *pcm) | |
| { | |
| snd_pcm_ioplug_hw_ptr_update(pcm); | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_reset(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| io->data->appl_ptr = 0; | |
| io->data->hw_ptr = 0; | |
| io->last_hw = 0; | |
| io->avail_max = 0; | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_prepare(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err = 0; | |
| snd_pcm_ioplug_reset(pcm); | |
| if (io->data->callback->prepare) { | |
| snd_pcm_unlock(pcm); /* to avoid deadlock */ | |
| err = io->data->callback->prepare(io->data); | |
| snd_pcm_lock(pcm); | |
| } | |
| if (err < 0) | |
| return err; | |
| io->data->state = SND_PCM_STATE_PREPARED; | |
| return err; | |
| } | |
| static const int hw_params_type[SND_PCM_IOPLUG_HW_PARAMS] = { | |
| [SND_PCM_IOPLUG_HW_ACCESS] = SND_PCM_HW_PARAM_ACCESS, | |
| [SND_PCM_IOPLUG_HW_FORMAT] = SND_PCM_HW_PARAM_FORMAT, | |
| [SND_PCM_IOPLUG_HW_CHANNELS] = SND_PCM_HW_PARAM_CHANNELS, | |
| [SND_PCM_IOPLUG_HW_RATE] = SND_PCM_HW_PARAM_RATE, | |
| [SND_PCM_IOPLUG_HW_PERIOD_BYTES] = SND_PCM_HW_PARAM_PERIOD_BYTES, | |
| [SND_PCM_IOPLUG_HW_BUFFER_BYTES] = SND_PCM_HW_PARAM_BUFFER_BYTES, | |
| [SND_PCM_IOPLUG_HW_PERIODS] = SND_PCM_HW_PARAM_PERIODS, | |
| }; | |
| /* x = a * b */ | |
| static int rule_mul(snd_pcm_hw_params_t *params, int x, int a, int b) | |
| { | |
| snd_interval_t t; | |
| snd_interval_mul(hw_param_interval(params, a), | |
| hw_param_interval(params, b), &t); | |
| return snd_interval_refine(hw_param_interval(params, x), &t); | |
| } | |
| /* x = a / b */ | |
| static int rule_div(snd_pcm_hw_params_t *params, int x, int a, int b) | |
| { | |
| snd_interval_t t; | |
| snd_interval_div(hw_param_interval(params, a), | |
| hw_param_interval(params, b), &t); | |
| return snd_interval_refine(hw_param_interval(params, x), &t); | |
| } | |
| /* x = a * b / k */ | |
| static int rule_muldivk(snd_pcm_hw_params_t *params, int x, int a, int b, int k) | |
| { | |
| snd_interval_t t; | |
| snd_interval_muldivk(hw_param_interval(params, a), | |
| hw_param_interval(params, b), k, &t); | |
| return snd_interval_refine(hw_param_interval(params, x), &t); | |
| } | |
| /* x = a * k / b */ | |
| static int rule_mulkdiv(snd_pcm_hw_params_t *params, int x, int a, int k, int b) | |
| { | |
| snd_interval_t t; | |
| snd_interval_mulkdiv(hw_param_interval(params, a), k, | |
| hw_param_interval(params, b), &t); | |
| return snd_interval_refine(hw_param_interval(params, x), &t); | |
| } | |
| #if 0 | |
| static void dump_parm(snd_pcm_hw_params_t *params) | |
| { | |
| snd_output_t *log; | |
| snd_output_stdio_attach(&log, stderr, 0); | |
| snd_pcm_hw_params_dump(params, log); | |
| snd_output_close(log); | |
| } | |
| #endif | |
| /* refine *_TIME and *_SIZE, then update *_BYTES */ | |
| static int refine_time_and_size(snd_pcm_hw_params_t *params, | |
| int time, int size, int bytes) | |
| { | |
| int err, change1 = 0; | |
| /* size = time * rate / 1000000 */ | |
| err = rule_muldivk(params, size, time, | |
| SND_PCM_HW_PARAM_RATE, 1000000); | |
| if (err < 0) | |
| return err; | |
| change1 |= err; | |
| /* bytes = size * framebits / 8 */ | |
| err = rule_muldivk(params, bytes, size, | |
| SND_PCM_HW_PARAM_FRAME_BITS, 8); | |
| if (err < 0) | |
| return err; | |
| change1 |= err; | |
| return change1; | |
| } | |
| /* refine *_TIME and *_SIZE from *_BYTES */ | |
| static int refine_back_time_and_size(snd_pcm_hw_params_t *params, | |
| int time, int size, int bytes) | |
| { | |
| int err; | |
| /* size = bytes * 8 / framebits */ | |
| err = rule_mulkdiv(params, size, bytes, 8, SND_PCM_HW_PARAM_FRAME_BITS); | |
| if (err < 0) | |
| return err; | |
| /* time = size * 1000000 / rate */ | |
| err = rule_mulkdiv(params, time, size, 1000000, SND_PCM_HW_PARAM_RATE); | |
| if (err < 0) | |
| return err; | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| int change = 0, change1, change2, err; | |
| ioplug_priv_t *io = pcm->private_data; | |
| struct snd_ext_parm *p; | |
| unsigned int i; | |
| /* access, format */ | |
| for (i = SND_PCM_IOPLUG_HW_ACCESS; i <= SND_PCM_IOPLUG_HW_FORMAT; i++) { | |
| err = snd_ext_parm_mask_refine(hw_param_mask(params, hw_params_type[i]), | |
| io->params, i); | |
| if (err < 0) | |
| return err; | |
| change |= err; | |
| } | |
| /* channels, rate */ | |
| for (; i <= SND_PCM_IOPLUG_HW_RATE; i++) { | |
| err = snd_ext_parm_interval_refine(hw_param_interval(params, hw_params_type[i]), | |
| io->params, i); | |
| if (err < 0) | |
| return err; | |
| change |= err; | |
| } | |
| if (params->rmask & ((1 << SND_PCM_HW_PARAM_ACCESS) | | |
| (1 << SND_PCM_HW_PARAM_FORMAT) | | |
| (1 << SND_PCM_HW_PARAM_SUBFORMAT) | | |
| (1 << SND_PCM_HW_PARAM_CHANNELS) | | |
| (1 << SND_PCM_HW_PARAM_RATE))) { | |
| err = snd_pcm_hw_refine_soft(pcm, params); | |
| if (err < 0) | |
| return err; | |
| change |= err; | |
| } | |
| change1 = refine_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME, | |
| SND_PCM_HW_PARAM_PERIOD_SIZE, | |
| SND_PCM_HW_PARAM_PERIOD_BYTES); | |
| if (change1 < 0) | |
| return change1; | |
| err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_PERIOD_BYTES), | |
| io->params, SND_PCM_IOPLUG_HW_PERIOD_BYTES); | |
| if (err < 0) | |
| return err; | |
| change1 |= err; | |
| if (change1) { | |
| change |= change1; | |
| err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME, | |
| SND_PCM_HW_PARAM_PERIOD_SIZE, | |
| SND_PCM_HW_PARAM_PERIOD_BYTES); | |
| if (err < 0) | |
| return err; | |
| } | |
| change1 = refine_time_and_size(params, SND_PCM_HW_PARAM_BUFFER_TIME, | |
| SND_PCM_HW_PARAM_BUFFER_SIZE, | |
| SND_PCM_HW_PARAM_BUFFER_BYTES); | |
| if (change1 < 0) | |
| return change1; | |
| change |= change1; | |
| do { | |
| change2 = 0; | |
| err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_BUFFER_BYTES), | |
| io->params, SND_PCM_IOPLUG_HW_BUFFER_BYTES); | |
| if (err < 0) | |
| return err; | |
| change2 |= err; | |
| /* periods = buffer_bytes / period_bytes */ | |
| err = rule_div(params, SND_PCM_HW_PARAM_PERIODS, | |
| SND_PCM_HW_PARAM_BUFFER_BYTES, | |
| SND_PCM_HW_PARAM_PERIOD_BYTES); | |
| if (err < 0) | |
| return err; | |
| change2 |= err; | |
| err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_PERIODS), | |
| io->params, SND_PCM_IOPLUG_HW_PERIODS); | |
| if (err < 0) | |
| return err; | |
| change2 |= err; | |
| /* buffer_bytes = periods * period_bytes */ | |
| err = rule_mul(params, SND_PCM_HW_PARAM_BUFFER_BYTES, | |
| SND_PCM_HW_PARAM_PERIOD_BYTES, | |
| SND_PCM_HW_PARAM_PERIODS); | |
| if (err < 0) | |
| return err; | |
| change2 |= err; | |
| change1 |= change2; | |
| } while (change2); | |
| change |= change1; | |
| if (change1) { | |
| err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_BUFFER_TIME, | |
| SND_PCM_HW_PARAM_BUFFER_SIZE, | |
| SND_PCM_HW_PARAM_BUFFER_BYTES); | |
| if (err < 0) | |
| return err; | |
| } | |
| /* period_bytes = buffer_bytes / periods */ | |
| err = rule_div(params, SND_PCM_HW_PARAM_PERIOD_BYTES, | |
| SND_PCM_HW_PARAM_BUFFER_BYTES, | |
| SND_PCM_HW_PARAM_PERIODS); | |
| if (err < 0) | |
| return err; | |
| if (err) { | |
| /* update period_size and period_time */ | |
| change |= err; | |
| err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_PERIOD_BYTES), | |
| io->params, SND_PCM_IOPLUG_HW_PERIOD_BYTES); | |
| if (err < 0) | |
| return err; | |
| err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME, | |
| SND_PCM_HW_PARAM_PERIOD_SIZE, | |
| SND_PCM_HW_PARAM_PERIOD_BYTES); | |
| if (err < 0) | |
| return err; | |
| } | |
| params->info = SND_PCM_INFO_BLOCK_TRANSFER; | |
| p = &io->params[SND_PCM_IOPLUG_HW_ACCESS]; | |
| if (p->active) { | |
| for (i = 0; i < p->num_list; i++) | |
| switch (p->list[i]) { | |
| case SND_PCM_ACCESS_MMAP_INTERLEAVED: | |
| case SND_PCM_ACCESS_RW_INTERLEAVED: | |
| params->info |= SND_PCM_INFO_INTERLEAVED; | |
| break; | |
| case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: | |
| case SND_PCM_ACCESS_RW_NONINTERLEAVED: | |
| params->info |= SND_PCM_INFO_NONINTERLEAVED; | |
| break; | |
| } | |
| } | |
| if (io->data->callback->pause) | |
| params->info |= SND_PCM_INFO_PAUSE; | |
| if (io->data->callback->resume) | |
| params->info |= SND_PCM_INFO_RESUME; | |
| #if 0 | |
| fprintf(stderr, "XXX\n"); | |
| dump_parm(params); | |
| #endif | |
| return change; | |
| } | |
| static int snd_pcm_ioplug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err; | |
| INTERNAL(snd_pcm_hw_params_get_access)(params, &io->data->access); | |
| INTERNAL(snd_pcm_hw_params_get_format)(params, &io->data->format); | |
| INTERNAL(snd_pcm_hw_params_get_channels)(params, &io->data->channels); | |
| INTERNAL(snd_pcm_hw_params_get_rate)(params, &io->data->rate, 0); | |
| INTERNAL(snd_pcm_hw_params_get_period_size)(params, &io->data->period_size, 0); | |
| INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &io->data->buffer_size); | |
| if (io->data->callback->hw_params) { | |
| err = io->data->callback->hw_params(io->data, params); | |
| if (err < 0) | |
| return err; | |
| INTERNAL(snd_pcm_hw_params_get_access)(params, &io->data->access); | |
| INTERNAL(snd_pcm_hw_params_get_format)(params, &io->data->format); | |
| INTERNAL(snd_pcm_hw_params_get_channels)(params, &io->data->channels); | |
| INTERNAL(snd_pcm_hw_params_get_rate)(params, &io->data->rate, 0); | |
| INTERNAL(snd_pcm_hw_params_get_period_size)(params, &io->data->period_size, 0); | |
| INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &io->data->buffer_size); | |
| } | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_hw_free(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->callback->hw_free) | |
| return io->data->callback->hw_free(io->data); | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err; | |
| if (!io->data->callback->sw_params) | |
| return 0; | |
| snd_pcm_unlock(pcm); /* to avoid deadlock */ | |
| err = io->data->callback->sw_params(io->data, params); | |
| snd_pcm_lock(pcm); | |
| return err; | |
| } | |
| static int snd_pcm_ioplug_start(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err; | |
| if (io->data->state != SND_PCM_STATE_PREPARED) | |
| return -EBADFD; | |
| err = io->data->callback->start(io->data); | |
| if (err < 0) | |
| return err; | |
| gettimestamp(&io->trigger_tstamp, pcm->tstamp_type); | |
| io->data->state = SND_PCM_STATE_RUNNING; | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_drop(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->state == SND_PCM_STATE_OPEN) | |
| return -EBADFD; | |
| io->data->callback->stop(io->data); | |
| gettimestamp(&io->trigger_tstamp, pcm->tstamp_type); | |
| io->data->state = SND_PCM_STATE_SETUP; | |
| return 0; | |
| } | |
| static int ioplug_drain_via_poll(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| while (io->data->state == SND_PCM_STATE_DRAINING) { | |
| snd_pcm_ioplug_hw_ptr_update(pcm); | |
| if (io->data->state != SND_PCM_STATE_DRAINING) | |
| break; | |
| /* in non-blocking mode, let application to poll() by itself */ | |
| if (io->data->nonblock) | |
| return -EAGAIN; | |
| if (snd_pcm_wait_nocheck(pcm, SND_PCM_WAIT_DRAIN) < 0) | |
| break; | |
| } | |
| return 0; /* force to drop at error */ | |
| } | |
| /* need own locking */ | |
| static int snd_pcm_ioplug_drain(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err = 0; | |
| snd_pcm_lock(pcm); | |
| switch (io->data->state) { | |
| case SND_PCM_STATE_OPEN: | |
| case SND_PCM_STATE_DISCONNECTED: | |
| case SND_PCM_STATE_SUSPENDED: | |
| snd_pcm_unlock(pcm); | |
| return -EBADFD; | |
| case SND_PCM_STATE_PREPARED: | |
| if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { | |
| if (!io->data->callback->drain) { | |
| err = snd_pcm_ioplug_start(pcm); | |
| if (err < 0) | |
| goto unlock; | |
| } | |
| io->data->state = SND_PCM_STATE_DRAINING; | |
| } | |
| break; | |
| case SND_PCM_STATE_RUNNING: | |
| io->data->state = SND_PCM_STATE_DRAINING; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (io->data->state == SND_PCM_STATE_DRAINING) { | |
| if (io->data->callback->drain) { | |
| snd_pcm_unlock(pcm); /* let plugin own locking */ | |
| err = io->data->callback->drain(io->data); | |
| snd_pcm_lock(pcm); | |
| } else { | |
| err = ioplug_drain_via_poll(pcm); | |
| } | |
| } | |
| unlock: | |
| if (!err && io->data->state != SND_PCM_STATE_SETUP) | |
| snd_pcm_ioplug_drop(pcm); | |
| snd_pcm_unlock(pcm); | |
| return err; | |
| } | |
| static int snd_pcm_ioplug_pause(snd_pcm_t *pcm, int enable) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| static const snd_pcm_state_t states[2] = { | |
| SND_PCM_STATE_RUNNING, SND_PCM_STATE_PAUSED | |
| }; | |
| int prev, err; | |
| prev = !enable; | |
| enable = !prev; | |
| if (io->data->state != states[prev]) | |
| return -EBADFD; | |
| if (io->data->callback->pause) { | |
| err = io->data->callback->pause(io->data, enable); | |
| if (err < 0) | |
| return err; | |
| } | |
| io->data->state = states[enable]; | |
| return 0; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_rewindable(snd_pcm_t *pcm) | |
| { | |
| return snd_pcm_mmap_hw_rewindable(pcm); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_mmap_appl_backward(pcm, frames); | |
| return frames; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_forwardable(snd_pcm_t *pcm) | |
| { | |
| return snd_pcm_mmap_avail(pcm); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | |
| { | |
| snd_pcm_mmap_appl_forward(pcm, frames); | |
| return frames; | |
| } | |
| /* need own locking */ | |
| static int snd_pcm_ioplug_resume(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->callback->resume) | |
| io->data->callback->resume(io->data); | |
| return 0; | |
| } | |
| /* called in lock */ | |
| static snd_pcm_sframes_t ioplug_priv_transfer_areas(snd_pcm_t *pcm, | |
| const snd_pcm_channel_area_t *areas, | |
| snd_pcm_uframes_t offset, | |
| snd_pcm_uframes_t size) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| snd_pcm_sframes_t result; | |
| if (! size) | |
| return 0; | |
| if (io->data->callback->transfer) | |
| result = io->data->callback->transfer(io->data, areas, offset, size); | |
| else | |
| result = size; | |
| if (result > 0) | |
| snd_pcm_mmap_appl_forward(pcm, result); | |
| return result; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) | |
| { | |
| if (pcm->mmap_rw) | |
| return snd_pcm_mmap_writei(pcm, buffer, size); | |
| else { | |
| snd_pcm_channel_area_t areas[pcm->channels]; | |
| snd_pcm_areas_from_buf(pcm, areas, (void*)buffer); | |
| return snd_pcm_write_areas(pcm, areas, 0, size, | |
| ioplug_priv_transfer_areas); | |
| } | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) | |
| { | |
| if (pcm->mmap_rw) | |
| return snd_pcm_mmap_writen(pcm, bufs, size); | |
| else { | |
| snd_pcm_channel_area_t areas[pcm->channels]; | |
| snd_pcm_areas_from_bufs(pcm, areas, bufs); | |
| return snd_pcm_write_areas(pcm, areas, 0, size, | |
| ioplug_priv_transfer_areas); | |
| } | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) | |
| { | |
| if (pcm->mmap_rw) | |
| return snd_pcm_mmap_readi(pcm, buffer, size); | |
| else { | |
| snd_pcm_channel_area_t areas[pcm->channels]; | |
| snd_pcm_areas_from_buf(pcm, areas, buffer); | |
| return snd_pcm_read_areas(pcm, areas, 0, size, | |
| ioplug_priv_transfer_areas); | |
| } | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) | |
| { | |
| if (pcm->mmap_rw) | |
| return snd_pcm_mmap_readn(pcm, bufs, size); | |
| else { | |
| snd_pcm_channel_area_t areas[pcm->channels]; | |
| snd_pcm_areas_from_bufs(pcm, areas, bufs); | |
| return snd_pcm_read_areas(pcm, areas, 0, size, | |
| ioplug_priv_transfer_areas); | |
| } | |
| } | |
| static int snd_pcm_ioplug_mmap_begin_capture(snd_pcm_t *pcm, | |
| const snd_pcm_channel_area_t **areas, | |
| snd_pcm_uframes_t *offset, | |
| snd_pcm_uframes_t *frames) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err; | |
| err = __snd_pcm_mmap_begin_generic(pcm, areas, offset, frames); | |
| if (err < 0) | |
| return err; | |
| if (io->data->callback->transfer && | |
| pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED && | |
| pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) { | |
| snd_pcm_sframes_t result; | |
| result = io->data->callback->transfer(io->data, *areas, *offset, *frames); | |
| if (result < 0) | |
| return result; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_ioplug_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, | |
| snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames) | |
| { | |
| if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | |
| return __snd_pcm_mmap_begin_generic(pcm, areas, offset, frames); | |
| return snd_pcm_ioplug_mmap_begin_capture(pcm, areas, offset, frames); | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_mmap_commit(snd_pcm_t *pcm, | |
| snd_pcm_uframes_t offset, | |
| snd_pcm_uframes_t size) | |
| { | |
| if (pcm->stream == SND_PCM_STREAM_PLAYBACK && | |
| pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED && | |
| pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) { | |
| const snd_pcm_channel_area_t *areas; | |
| snd_pcm_uframes_t ofs, frames = size; | |
| __snd_pcm_mmap_begin_generic(pcm, &areas, &ofs, &frames); | |
| if (ofs != offset) | |
| return -EIO; | |
| return ioplug_priv_transfer_areas(pcm, areas, offset, frames); | |
| } | |
| snd_pcm_mmap_appl_forward(pcm, size); | |
| return size; | |
| } | |
| static snd_pcm_sframes_t snd_pcm_ioplug_avail_update(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| snd_pcm_uframes_t avail; | |
| snd_pcm_ioplug_hw_ptr_update(pcm); | |
| if (io->data->state == SND_PCM_STATE_XRUN) | |
| return -EPIPE; | |
| avail = snd_pcm_mmap_avail(pcm); | |
| if (avail > io->avail_max) | |
| io->avail_max = avail; | |
| return (snd_pcm_sframes_t)avail; | |
| } | |
| static int snd_pcm_ioplug_nonblock(snd_pcm_t *pcm, int nonblock) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| io->data->nonblock = nonblock; | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_poll_descriptors_count(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err = 1; | |
| if (io->data->callback->poll_descriptors_count) { | |
| snd_pcm_unlock(pcm); /* to avoid deadlock */ | |
| err = io->data->callback->poll_descriptors_count(io->data); | |
| snd_pcm_lock(pcm); | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_ioplug_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err; | |
| if (io->data->callback->poll_descriptors) { | |
| snd_pcm_unlock(pcm); /* to avoid deadlock */ | |
| err = io->data->callback->poll_descriptors(io->data, pfds, space); | |
| snd_pcm_lock(pcm); | |
| return err; | |
| } | |
| if (pcm->poll_fd < 0) | |
| return -EIO; | |
| if (space >= 1 && pfds) { | |
| pfds->fd = pcm->poll_fd; | |
| pfds->events = pcm->poll_events | POLLERR | POLLNVAL; | |
| } else { | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| static int snd_pcm_ioplug_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| int err; | |
| if (io->data->callback->poll_revents) { | |
| snd_pcm_unlock(pcm); /* to avoid deadlock */ | |
| err = io->data->callback->poll_revents(io->data, pfds, nfds, revents); | |
| snd_pcm_lock(pcm); | |
| } else { | |
| *revents = pfds->revents; | |
| err = 0; | |
| } | |
| return err; | |
| } | |
| static int snd_pcm_ioplug_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static int snd_pcm_ioplug_async(snd_pcm_t *pcm ATTRIBUTE_UNUSED, | |
| int sig ATTRIBUTE_UNUSED, | |
| pid_t pid ATTRIBUTE_UNUSED) | |
| { | |
| return -ENOSYS; | |
| } | |
| static int snd_pcm_ioplug_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) | |
| { | |
| return 0; | |
| } | |
| static snd_pcm_chmap_query_t **snd_pcm_ioplug_query_chmaps(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->version >= 0x010002 && | |
| io->data->callback->query_chmaps) | |
| return io->data->callback->query_chmaps(io->data); | |
| return NULL; | |
| } | |
| static snd_pcm_chmap_t *snd_pcm_ioplug_get_chmap(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->version >= 0x010002 && | |
| io->data->callback->get_chmap) | |
| return io->data->callback->get_chmap(io->data); | |
| return NULL; | |
| } | |
| static int snd_pcm_ioplug_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->version >= 0x010002 && | |
| io->data->callback->set_chmap) | |
| return io->data->callback->set_chmap(io->data, map); | |
| return -ENXIO; | |
| } | |
| static void snd_pcm_ioplug_dump(snd_pcm_t *pcm, snd_output_t *out) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| if (io->data->callback->dump) | |
| io->data->callback->dump(io->data, out); | |
| else { | |
| if (io->data->name) | |
| snd_output_printf(out, "%s\n", io->data->name); | |
| else | |
| snd_output_printf(out, "IO-PCM Plugin\n"); | |
| if (pcm->setup) { | |
| snd_output_printf(out, "Its setup is:\n"); | |
| snd_pcm_dump_setup(pcm, out); | |
| } | |
| } | |
| } | |
| static void clear_io_params(ioplug_priv_t *io) | |
| { | |
| int i; | |
| for (i = 0; i < SND_PCM_IOPLUG_HW_PARAMS; i++) | |
| snd_ext_parm_clear(&io->params[i]); | |
| } | |
| static int snd_pcm_ioplug_close(snd_pcm_t *pcm) | |
| { | |
| ioplug_priv_t *io = pcm->private_data; | |
| clear_io_params(io); | |
| if (io->data->callback->close) | |
| io->data->callback->close(io->data); | |
| free(io); | |
| return 0; | |
| } | |
| static const snd_pcm_ops_t snd_pcm_ioplug_ops = { | |
| .close = snd_pcm_ioplug_close, | |
| .nonblock = snd_pcm_ioplug_nonblock, | |
| .async = snd_pcm_ioplug_async, | |
| .info = snd_pcm_ioplug_info, | |
| .hw_refine = snd_pcm_ioplug_hw_refine, | |
| .hw_params = snd_pcm_ioplug_hw_params, | |
| .hw_free = snd_pcm_ioplug_hw_free, | |
| .sw_params = snd_pcm_ioplug_sw_params, | |
| .channel_info = snd_pcm_ioplug_channel_info, | |
| .dump = snd_pcm_ioplug_dump, | |
| .mmap = snd_pcm_ioplug_mmap, | |
| .munmap = snd_pcm_ioplug_munmap, | |
| .query_chmaps = snd_pcm_ioplug_query_chmaps, | |
| .get_chmap = snd_pcm_ioplug_get_chmap, | |
| .set_chmap = snd_pcm_ioplug_set_chmap, | |
| }; | |
| static const snd_pcm_fast_ops_t snd_pcm_ioplug_fast_ops = { | |
| .status = snd_pcm_ioplug_status, | |
| .prepare = snd_pcm_ioplug_prepare, | |
| .reset = snd_pcm_ioplug_reset, | |
| .start = snd_pcm_ioplug_start, | |
| .drop = snd_pcm_ioplug_drop, | |
| .drain = snd_pcm_ioplug_drain, | |
| .pause = snd_pcm_ioplug_pause, | |
| .state = snd_pcm_ioplug_state, | |
| .hwsync = snd_pcm_ioplug_hwsync, | |
| .delay = snd_pcm_ioplug_delay, | |
| .resume = snd_pcm_ioplug_resume, | |
| .link = NULL, | |
| .link_slaves = NULL, | |
| .unlink = NULL, | |
| .rewindable = snd_pcm_ioplug_rewindable, | |
| .rewind = snd_pcm_ioplug_rewind, | |
| .forwardable = snd_pcm_ioplug_forwardable, | |
| .forward = snd_pcm_ioplug_forward, | |
| .writei = snd_pcm_ioplug_writei, | |
| .writen = snd_pcm_ioplug_writen, | |
| .readi = snd_pcm_ioplug_readi, | |
| .readn = snd_pcm_ioplug_readn, | |
| .avail_update = snd_pcm_ioplug_avail_update, | |
| .mmap_commit = snd_pcm_ioplug_mmap_commit, | |
| .htimestamp = snd_pcm_generic_real_htimestamp, | |
| .poll_descriptors_count = snd_pcm_ioplug_poll_descriptors_count, | |
| .poll_descriptors = snd_pcm_ioplug_poll_descriptors, | |
| .poll_revents = snd_pcm_ioplug_poll_revents, | |
| .mmap_begin = snd_pcm_ioplug_mmap_begin, | |
| }; | |
| #endif /* !DOC_HIDDEN */ | |
| /* | |
| * Exported functions | |
| */ | |
| /*! \page pcm_external_plugins PCM External Plugin SDK | |
| \section pcm_ioplug External Plugin: I/O Plugin | |
| The I/O-type plugin is a PCM plugin to work as the input or output terminal point, | |
| i.e. as a user-space PCM driver. | |
| The new plugin is created via #snd_pcm_ioplug_create() function. | |
| The first argument is a pointer of the pluging information. Some of | |
| this struct must be initialized in prior to call | |
| #snd_pcm_ioplug_create(). Then the function fills other fields in | |
| return. The rest arguments, name, stream and mode, are usually | |
| identical with the values passed from the ALSA plugin constructor. | |
| The following fields are mandatory: version, name, callback. | |
| Otherfields are optional and should be initialized with zero. | |
| The constant #SND_PCM_IOPLUG_VERSION must be passed to the version | |
| field for the version check in alsa-lib. A non-NULL ASCII string | |
| has to be passed to the name field. The callback field contains the | |
| table of callback functions for this plugin (defined as | |
| #snd_pcm_ioplug_callback_t). | |
| flags field specifies the optional bit-flags. poll_fd and poll_events | |
| specify the poll file descriptor and the corresponding poll events | |
| (POLLIN, POLLOUT) for the plugin. If the plugin requires multiple | |
| poll descriptors or poll descriptor(s) dynamically varying, set | |
| poll_descriptors and poll_descriptors_count callbacks to the callback | |
| table. Then the poll_fd and poll_events field are ignored. | |
| mmap_rw specifies whether the plugin behaves in the pseudo mmap mode. | |
| When this value is set to 1, the plugin creates always a local buffer | |
| and performs read/write calls using this buffer as if it's mmapped. | |
| The address of local buffer can be obtained via | |
| #snd_pcm_ioplug_mmap_areas() function. | |
| When poll_fd, poll_events and mmap_rw fields are changed after | |
| #snd_pcm_ioplug_create(), call #snd_pcm_ioplug_reinit_status() to | |
| reflect the changes. | |
| The driver can set an arbitrary value (pointer) to private_data | |
| field to refer its own data in the callbacks. | |
| The rest fields are filled by #snd_pcm_ioplug_create(). The pcm field | |
| is the resultant PCM handle. The others are the current status of the | |
| PCM. | |
| The callback functions in #snd_pcm_ioplug_callback_t define the real | |
| behavior of the driver. | |
| At least, start, stop and pointer callbacks must be given. Other | |
| callbacks are optional. The start and stop callbacks are called when | |
| the PCM stream is started and stopped, repsectively. The pointer | |
| callback returns the current DMA position, which may be called at any | |
| time. | |
| The transfer callback is called when any data transfer happens. It | |
| receives the area array, offset and the size to transfer. The area | |
| array contains the array of snd_pcm_channel_area_t with the elements | |
| of number of channels. | |
| When the PCM is closed, close callback is called. If the driver | |
| allocates any internal buffers, they should be released in this | |
| callback. The hw_params and hw_free callbacks are called when | |
| hw_params are set and reset, respectively. Note that they may be | |
| called multiple times according to the application. Similarly, | |
| sw_params callback is called when sw_params is set or changed. | |
| The prepare, drain, pause and resume callbacks are called when | |
| #snd_pcm_prepare(), #snd_pcm_drain(), #snd_pcm_pause(), and | |
| #snd_pcm_resume() are called. The poll_descriptors_count and | |
| poll_descriptors callbacks are used to return the multiple or dynamic | |
| poll descriptors as mentioned above. The poll_revents callback is | |
| used to modify poll events. If the driver needs to mangle the native | |
| poll events to proper poll events for PCM, you can do it in this | |
| callback. | |
| Finally, the dump callback is used to print the status of the plugin. | |
| Note that some callbacks (start, stop, pointer, transfer and pause) | |
| may be called inside the internal pthread mutex, and they shouldn't | |
| call the PCM functions again unnecessarily from the callback itself; | |
| otherwise it may lead to a deadlock. | |
| The hw_params constraints can be defined via either | |
| #snd_pcm_ioplug_set_param_minmax() and #snd_pcm_ioplug_set_param_list() | |
| functions after calling #snd_pcm_ioplug_create(). | |
| The former defines the minimal and maximal acceptable values for the | |
| given hw_params parameter (SND_PCM_IOPLUG_HW_XXX). | |
| This function can't be used for the format parameter. The latter | |
| function specifies the available parameter values as the list. | |
| To clear the parameter constraints, call #snd_pcm_ioplug_params_reset() function. | |
| */ | |
| /** | |
| * \brief Create an ioplug instance | |
| * \param ioplug the ioplug handle | |
| * \param name name of PCM | |
| * \param stream stream direction | |
| * \param mode PCM open mode | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Creates the ioplug instance. | |
| * | |
| * The callback is the mandatory field of ioplug handle. At least, start, stop and | |
| * pointer callbacks must be set before calling this function. | |
| * | |
| */ | |
| int snd_pcm_ioplug_create(snd_pcm_ioplug_t *ioplug, const char *name, | |
| snd_pcm_stream_t stream, int mode) | |
| { | |
| ioplug_priv_t *io; | |
| int err; | |
| snd_pcm_t *pcm; | |
| assert(ioplug && ioplug->callback); | |
| assert(ioplug->callback->start && | |
| ioplug->callback->stop && | |
| ioplug->callback->pointer); | |
| /* We support 1.0.0 to current */ | |
| if (ioplug->version < 0x010000 || | |
| ioplug->version > SND_PCM_IOPLUG_VERSION) { | |
| SNDERR("ioplug: Plugin version mismatch: 0x%x", | |
| ioplug->version); | |
| return -ENXIO; | |
| } | |
| io = calloc(1, sizeof(*io)); | |
| if (! io) | |
| return -ENOMEM; | |
| io->data = ioplug; | |
| ioplug->state = SND_PCM_STATE_OPEN; | |
| ioplug->stream = stream; | |
| err = snd_pcm_new(&pcm, SND_PCM_TYPE_IOPLUG, name, stream, mode); | |
| if (err < 0) { | |
| free(io); | |
| return err; | |
| } | |
| ioplug->pcm = pcm; | |
| pcm->ops = &snd_pcm_ioplug_ops; | |
| pcm->fast_ops = &snd_pcm_ioplug_fast_ops; | |
| pcm->private_data = io; | |
| snd_pcm_set_hw_ptr(pcm, &ioplug->hw_ptr, -1, 0); | |
| snd_pcm_set_appl_ptr(pcm, &ioplug->appl_ptr, -1, 0); | |
| snd_pcm_ioplug_reinit_status(ioplug); | |
| return 0; | |
| } | |
| /** | |
| * \brief Delete the ioplug instance | |
| * \param ioplug the ioplug handle | |
| * \return 0 if successful, or a negative error code | |
| */ | |
| int snd_pcm_ioplug_delete(snd_pcm_ioplug_t *ioplug) | |
| { | |
| return snd_pcm_close(ioplug->pcm); | |
| } | |
| /** | |
| * \brief Reset ioplug parameters | |
| * \param ioplug the ioplug handle | |
| * | |
| * Resets the all parameters for the given ioplug handle. | |
| */ | |
| void snd_pcm_ioplug_params_reset(snd_pcm_ioplug_t *ioplug) | |
| { | |
| ioplug_priv_t *io = ioplug->pcm->private_data; | |
| clear_io_params(io); | |
| } | |
| /** | |
| * \brief Set parameter as the list | |
| * \param ioplug the ioplug handle | |
| * \param type parameter type | |
| * \param num_list number of available values | |
| * \param list the list of available values | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Sets the parameter as the list. | |
| * The available values of the given parameter type is restricted to the ones of the given list. | |
| */ | |
| int snd_pcm_ioplug_set_param_list(snd_pcm_ioplug_t *ioplug, int type, unsigned int num_list, const unsigned int *list) | |
| { | |
| ioplug_priv_t *io = ioplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_IOPLUG_HW_PARAMS) { | |
| SNDERR("IOPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| if (type == SND_PCM_IOPLUG_HW_PERIODS) | |
| io->params[type].integer = 1; | |
| return snd_ext_parm_set_list(&io->params[type], num_list, list); | |
| } | |
| /** | |
| * \brief Set parameter as the min/max values | |
| * \param ioplug the ioplug handle | |
| * \param type parameter type | |
| * \param min the minimum value | |
| * \param max the maximum value | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Sets the parameter as the min/max values. | |
| * The available values of the given parameter type is restricted between the given | |
| * minimum and maximum values. | |
| */ | |
| int snd_pcm_ioplug_set_param_minmax(snd_pcm_ioplug_t *ioplug, int type, unsigned int min, unsigned int max) | |
| { | |
| ioplug_priv_t *io = ioplug->pcm->private_data; | |
| if (type < 0 || type >= SND_PCM_IOPLUG_HW_PARAMS) { | |
| SNDERR("IOPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| if (type == SND_PCM_IOPLUG_HW_ACCESS || type == SND_PCM_IOPLUG_HW_FORMAT) { | |
| SNDERR("IOPLUG: invalid parameter type %d", type); | |
| return -EINVAL; | |
| } | |
| if (type == SND_PCM_IOPLUG_HW_PERIODS) | |
| io->params[type].integer = 1; | |
| return snd_ext_parm_set_minmax(&io->params[type], min, max); | |
| } | |
| /** | |
| * \brief Reinitialize the poll and mmap status | |
| * \param ioplug the ioplug handle | |
| * \return 0 if successful, or a negative error code | |
| * | |
| * Reinitializes the poll and the mmap status of the PCM. | |
| * Call this function to propagate the status change in the ioplug instance to | |
| * its PCM internals. | |
| */ | |
| int snd_pcm_ioplug_reinit_status(snd_pcm_ioplug_t *ioplug) | |
| { | |
| ioplug->pcm->poll_fd = ioplug->poll_fd; | |
| ioplug->pcm->poll_events = ioplug->poll_events; | |
| if (ioplug->flags & SND_PCM_IOPLUG_FLAG_MONOTONIC) | |
| ioplug->pcm->tstamp_type = SND_PCM_TSTAMP_TYPE_MONOTONIC; | |
| else | |
| ioplug->pcm->tstamp_type = SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY; | |
| ioplug->pcm->mmap_rw = ioplug->mmap_rw; | |
| return 0; | |
| } | |
| /** | |
| * \brief Get mmap area of ioplug | |
| * \param ioplug the ioplug handle | |
| * \return the mmap channel areas if available, or NULL | |
| * | |
| * Returns the mmap channel areas if available. When mmap_rw field is not set, | |
| * this function always returns NULL. | |
| */ | |
| const snd_pcm_channel_area_t *snd_pcm_ioplug_mmap_areas(snd_pcm_ioplug_t *ioplug) | |
| { | |
| if (ioplug->mmap_rw) | |
| return snd_pcm_mmap_areas(ioplug->pcm); | |
| return NULL; | |
| } | |
| /** | |
| * \brief Change the ioplug PCM status | |
| * \param ioplug the ioplug handle | |
| * \param state the PCM status | |
| * \return zero if successful or a negative error code | |
| * | |
| * Changes the PCM status of the ioplug to the given value. | |
| * This function can be used for external plugins to notify the status | |
| * change, e.g. XRUN. | |
| */ | |
| int snd_pcm_ioplug_set_state(snd_pcm_ioplug_t *ioplug, snd_pcm_state_t state) | |
| { | |
| ioplug->state = state; | |
| return 0; | |
| } | |
| /** | |
| * \brief Get the available frames. This function can be used to calculate the | |
| * the available frames before calling #snd_pcm_avail_update() | |
| * \param ioplug the ioplug handle | |
| * \param hw_ptr hardware pointer in frames | |
| * \param appl_ptr application pointer in frames | |
| * \return available frames for the application | |
| */ | |
| snd_pcm_uframes_t snd_pcm_ioplug_avail(const snd_pcm_ioplug_t * const ioplug, | |
| const snd_pcm_uframes_t hw_ptr, | |
| const snd_pcm_uframes_t appl_ptr) | |
| { | |
| return __snd_pcm_avail(ioplug->pcm, hw_ptr, appl_ptr); | |
| } | |
| /** | |
| * \brief Get the available frames. This function can be used to calculate the | |
| * the available frames before calling #snd_pcm_avail_update() | |
| * \param ioplug the ioplug handle | |
| * \param hw_ptr hardware pointer in frames | |
| * \param appl_ptr application pointer in frames | |
| * \return available frames for the hardware | |
| */ | |
| snd_pcm_uframes_t snd_pcm_ioplug_hw_avail(const snd_pcm_ioplug_t * const ioplug, | |
| const snd_pcm_uframes_t hw_ptr, | |
| const snd_pcm_uframes_t appl_ptr) | |
| { | |
| /* available data/space which can be transferred by the user | |
| * application | |
| */ | |
| const snd_pcm_uframes_t user_avail = snd_pcm_ioplug_avail(ioplug, | |
| hw_ptr, | |
| appl_ptr); | |
| if (user_avail > ioplug->pcm->buffer_size) { | |
| /* there was an Xrun */ | |
| return 0; | |
| } | |
| /* available data/space which can be transferred by the DMA */ | |
| return ioplug->pcm->buffer_size - user_avail; | |
| } |