/* * Copyright(C) 2018 "IoT.bzh" * Author Fulup Ar Foll * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http : //www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * reference : * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 * */ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" #include #include #include #include #include #include "time_utils.h" static int xrun(snd_pcm_t * pcm, int error); static int suspend(snd_pcm_t * pcm, int error); typedef enum { PCM_COPY_MUTE, PCM_COPY_UNMUTE, PCM_COPY_END, PCM_COPY_LAST // Do not put anything after } PcmCopyEventType; typedef struct { PcmCopyEventType eventType; } PcmCopyEvent; STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { int pcmSampleSize; switch (pcmFormat) { case SND_PCM_FORMAT_S8: case SND_PCM_FORMAT_U8: pcmSampleSize = 1; break; case SND_PCM_FORMAT_U16_LE: case SND_PCM_FORMAT_U16_BE: case SND_PCM_FORMAT_S16_LE: case SND_PCM_FORMAT_S16_BE: pcmSampleSize = 2; break; case SND_PCM_FORMAT_U24_LE: case SND_PCM_FORMAT_U24_BE: case SND_PCM_FORMAT_S24_LE: case SND_PCM_FORMAT_S24_BE: pcmSampleSize = 3; break; case SND_PCM_FORMAT_U32_LE: case SND_PCM_FORMAT_U32_BE: case SND_PCM_FORMAT_S32_LE: case SND_PCM_FORMAT_S32_BE: pcmSampleSize = 4; break; default: pcmSampleSize = 0; } return pcmSampleSize; } PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { int error; snd_pcm_hw_params_t *pxmHwParams; snd_pcm_sw_params_t *pxmSwParams; snd_pcm_format_t format; snd_pcm_access_t access; AlsaPcmHwInfoT * opts = pcm->params; const char * card = pcm->cid.cardid; const char * modeS = mode==SND_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE"; AFB_ApiDebug(mixer->api, "%s: mixer info %s uid %s, pcm %s, mode %s", __func__, mixer->info, mixer->uid, card, modeS); // retrieve hardware config from PCM snd_pcm_hw_params_alloca(&pxmHwParams); error = snd_pcm_hw_params_any(pcm->handle, pxmHwParams); if (error < 0) { AFB_ApiError(mixer->api, "%s: Failed to get parameters: %s", __func__, snd_strerror(error)); goto OnErrorExit; } AFB_ApiDebug(mixer->api, "(%s): PARAMS before:", card); AlsaDumpPcmParams(mixer, pxmHwParams); if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED; snd_pcm_hw_params_get_access(pxmHwParams, &access); error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); if (error) { AFB_ApiError(mixer->api, "%s (%s) set_access failed (ignore this error): mixer=%s access=%d Fail current=%d mode error=%s", __func__, card, mixer->uid, opts->access, access, snd_strerror(error)); //Fulup goto OnErrorExit; }; if (opts->format != SND_PCM_FORMAT_UNKNOWN) { snd_pcm_hw_params_get_format(pxmHwParams, &format); if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { AFB_ApiError(mixer->api, "%s (%s) mixer=%s Set_Format=%s (%d) FAILED current=%d error=%s", __func__, card, mixer->uid, opts->formatString, opts->format, format, snd_strerror(error)); AlsaDumpFormats(mixer, pcm->handle); goto OnErrorExit; } } unsigned int * channels = &opts->channels; if (*channels) { AFB_API_DEBUG(mixer->api, "%s: Attempt to set %d channels", __func__, *channels); if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, *channels)) < 0) { AFB_API_ERROR(mixer->api, "%s (%s): mixer=%s Set_Channels=%d Fail error=%s", __func__, card, mixer->uid, *channels, snd_strerror(error)); AlsaDumpPcmParams(mixer, pxmHwParams); goto OnErrorExit; }; AFB_API_DEBUG(mixer->api, "%s: CHANNELS SET TO %d", __func__, *channels); } if (opts->rate > 0 ) { AFB_ApiDebug(mixer->api,"%s (%s): set rate to %d", __func__, card, opts->rate); unsigned int pcmRate = opts->rate; /* Attempt to set the rate. Failing on a capture dev is acceptable */ error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0); if ( mode == SND_PCM_STREAM_PLAYBACK && error < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s FailSet_Rate=%d error=%s", __func__, card, mixer->uid, opts->rate, snd_strerror(error)); goto OnErrorExit; } // check we got requested rate if (mode == SND_PCM_STREAM_PLAYBACK && opts->rate != pcmRate) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Set_Rate Fail ask=%dHz get=%dHz", __func__, card, mixer->uid, pcmRate, opts->rate); goto OnErrorExit; } } /* The following code, that * 1) sets period time/size; buffer time/size hardware params * 2) sets start and stop threshold in software params * ... is taken as such from 'aplay' in alsa-utils */ unsigned buffer_time = 0; unsigned period_time = 0; snd_pcm_uframes_t buffer_frames = 0; snd_pcm_uframes_t period_frames = 0; error = snd_pcm_hw_params_get_buffer_time_max(pxmHwParams, &buffer_time, 0); AFB_ApiDebug(mixer->api, "(%s) HW_BUFFER_TIME MAX is %d", card, buffer_time); if (buffer_time > 500000) buffer_time = 500000; if (period_time == 0 && period_frames == 0) { if (buffer_time > 0) period_time = buffer_time / 4; else period_frames = buffer_frames / 4; } if (period_time > 0) { AFB_ApiDebug(mixer->api, "(%s) SET PERIOD TIME to %d", card, period_time); error = snd_pcm_hw_params_set_period_time_near(pcm->handle, pxmHwParams, &period_time, 0); } else { AFB_ApiDebug(mixer->api, "(%s) SET PERIOD SIZE...", card); error = snd_pcm_hw_params_set_period_size_near(pcm->handle, pxmHwParams, &period_frames, 0); } if (error < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Fail to set period in hwparams error=%s", __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } if (buffer_time > 0) { AFB_ApiDebug(mixer->api, "(%s) SET BUFFER TIME to %d", card, buffer_time); error = snd_pcm_hw_params_set_buffer_time_near(pcm->handle, pxmHwParams, &buffer_time, 0); } else { AFB_ApiDebug(mixer->api, "(%s) SET BUFFER SIZE to %ld...", card, buffer_frames); error = snd_pcm_hw_params_set_buffer_size_near(pcm->handle, pxmHwParams, &buffer_frames); } if (error < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Fail to set buffer in hwparams error=%s", __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } // store selected values if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Fail to apply hwparams error=%s", __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } AFB_ApiDebug(mixer->api, "(%s) PARAMS after:", card); AlsaDumpPcmParams(mixer, pxmHwParams); // check we effective hw params after optional format change snd_pcm_hw_params_get_channels(pxmHwParams, &opts->channels); snd_pcm_hw_params_get_format(pxmHwParams, &opts->format); snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0); AFB_ApiDebug(mixer->api, "(%s) rate is %d", card, opts->rate); opts->sampleSize = AlsaPeriodSize(opts->format); if (opts->sampleSize == 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Fail unsupported format format=%d", __func__, card, mixer->uid, opts->format); goto OnErrorExit; } snd_pcm_uframes_t chunk_size, buffer_size; snd_pcm_hw_params_get_period_size(pxmHwParams, &chunk_size, 0); snd_pcm_hw_params_get_buffer_size(pxmHwParams, &buffer_size); if (chunk_size == buffer_size) { AFB_ApiError(mixer->api, "(%s) Can't use period equal to buffer size (%lu == %lu)", card, chunk_size, buffer_size); goto OnErrorExit; } int avail_min = -1; size_t n; int rate = opts->rate; if (avail_min < 0) n = chunk_size; else n = (size_t) ((double)rate * avail_min / 1000000); // retrieve software config from PCM snd_pcm_sw_params_alloca(&pxmSwParams); snd_pcm_sw_params_current(pcm->handle, pxmSwParams); // available_min is the minimum number of frames available to read, // when the call to poll returns. Assume this is the same for playback (not checked that) AFB_API_DEBUG(mixer->api, "(%s) try AVAIL_MIN: %ld", card, n); if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, n)) < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Fail set_buffersize error=%s", __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; }; snd_pcm_sw_params_get_avail_min(pxmSwParams, &pcm->avail_min); AFB_API_DEBUG(mixer->api, "(%s) AVAIL_MIN set to : %ld", card, pcm->avail_min); int start_delay = 0; snd_pcm_uframes_t start_threshold; /* round up to closest transfer boundary. Start delay is in microseconds */ n = buffer_size; if (start_delay <= 0) { start_threshold = n + (size_t)((double)rate * start_delay / 1000000); } else start_threshold = (size_t)((double)rate * start_delay / 1000000); if (start_threshold < 1) start_threshold = 1; if (start_threshold > n/2) start_threshold = n/2; AFB_ApiDebug(mixer->api, "(%s) CALCULATED START THRESHOLD: %ld", card, start_threshold); if (mode == SND_PCM_STREAM_PLAYBACK) { start_threshold = 1; } AFB_ApiDebug(mixer->api, "(%s) %s: Set start threshold to %ld", card, modeS, start_threshold); error = snd_pcm_sw_params_set_start_threshold(pcm->handle, pxmSwParams, start_threshold); if (error < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s failed set start_threshold, error=%s", __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } // push software params into PCM if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) { AFB_ApiError(mixer->api, "%s (%s): mixer=%s Fail to push SW params error=%s", __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; }; AFB_ApiNotice(mixer->api, "%s (%s): mixer=%s Done channels=%d rate=%d format=%d access=%d ... DONE !", __func__, card, mixer->uid, opts->channels, opts->rate, opts->format, opts->access); return 0; OnErrorExit: return -1; } STATIC int AlsaPcmReadCB( AlsaPcmCopyHandleT * pcmCopyHandle) { char string[32]; snd_pcm_sframes_t availIn; snd_pcm_t * pcmIn = pcmCopyHandle->pcmIn->handle; alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf; snd_pcm_uframes_t bufSize = alsa_ringbuf_buffer_size(rbuf); int err; const char * uid = ALSA_PCM_UID(pcmIn, string); // do we have waiting frames ? availIn = snd_pcm_avail_update(pcmIn); if (availIn <= 0) { if (availIn == -EPIPE) { int ret = xrun(pcmIn, (int)availIn); AFB_ApiDebug(pcmCopyHandle->api, "XXX read EPIPE (recov=%d) {%s}!", ret, uid); // For some (undocumented...) reason, a start is mandatory. snd_pcm_start(pcmIn); } goto ExitOnSuccess; } while (true) { snd_pcm_sframes_t nbRead; pthread_mutex_lock(&pcmCopyHandle->mutex); snd_pcm_sframes_t remain = alsa_ringbuf_frames_remain_capacity(rbuf); if (remain <= 0) { pthread_mutex_unlock(&pcmCopyHandle->mutex); // Wake up the reader, in case it is sleeping, // that lets it an opportunity to pop something. sem_post(&pcmCopyHandle->sem); break; } if (remain < availIn) remain = availIn; char buf[remain*pcmCopyHandle->frame_size]; pthread_mutex_unlock(&pcmCopyHandle->mutex); nbRead = snd_pcm_readi(pcmIn, buf, remain); if (nbRead == 0) { break; } if (nbRead < 0) { if (nbRead== -EPIPE) { err = xrun(pcmIn, (int)nbRead); AFB_ApiDebug(pcmCopyHandle->api, "read EPIPE (%d), recov %d {%s}", ++pcmCopyHandle->read_err_count, err, uid); goto ExitOnSuccess; } else if (nbRead== -ESTRPIPE) { AFB_ApiDebug(pcmCopyHandle->api, "read ESTRPIPE"); if ((err = suspend(pcmIn, (int)nbRead)) < 0) goto ExitOnSuccess; nbRead = 0; } else { AFB_API_DEBUG(pcmCopyHandle->api, "read error: %ld (%s) {%s}", -nbRead, strerror((int)-nbRead), uid); goto ExitOnSuccess; } } pthread_mutex_lock(&pcmCopyHandle->mutex); alsa_ringbuf_frames_push(rbuf, buf, nbRead); snd_pcm_uframes_t used = alsa_ringbuf_frames_used(rbuf); pthread_mutex_unlock(&pcmCopyHandle->mutex); // Wait for having the buffer full enough before waking up the playback // else it will starve immediately. if (used > 0.8 * (double)bufSize) { sem_post(&pcmCopyHandle->sem); } availIn -= nbRead; // completed, we have read everything if (availIn <= 0) { break; } } ExitOnSuccess: return 0; } static int xrun( snd_pcm_t * pcm, int error) { int err; if ((err = snd_pcm_recover(pcm, error, 1)) < 0) { return err; } return 0; } static int suspend( snd_pcm_t * pcm, int error) { int err; while ((err = snd_pcm_resume(pcm)) == -EAGAIN) { usleep(1); } if (err < 0) return xrun(pcm, error); return 0; } static void readSuspend(AlsaPcmCopyHandleT * pcmCopyHandle) { // will be deaf pcmCopyHandle->saveFd = pcmCopyHandle->pollFdsIn[1].fd; pcmCopyHandle->pollFdsIn[1].fd = -1; AFB_ApiNotice(pcmCopyHandle->api, "capture muted"); } static void readResume(AlsaPcmCopyHandleT * pcmCopyHandle) { // undeaf it pcmCopyHandle->pollFdsIn[1].fd = pcmCopyHandle->saveFd; snd_pcm_prepare(pcmCopyHandle->pcmIn->handle); snd_pcm_start(pcmCopyHandle->pcmIn->handle); AFB_ApiNotice(pcmCopyHandle->api, "capture unmuted"); } static void *readThreadEntry(void *handle) { #define LOOP_TIMEOUT_MSEC 10*1000 /* 10 seconds */ AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; pcmCopyHandle->tid = (int) syscall(SYS_gettid); AFB_ApiNotice(pcmCopyHandle->api, "%s :%s/%d Started, muted=%d", __func__, pcmCopyHandle->info, pcmCopyHandle->tid, pcmCopyHandle->pcmIn->mute); struct pollfd * eventFd = &pcmCopyHandle->pollFdsIn[0]; struct pollfd * framePfds = &pcmCopyHandle->pollFdsIn[1]; eventFd->events = POLLIN; bool muted = pcmCopyHandle->pcmIn->mute; if (muted) readSuspend(pcmCopyHandle); /* loop until end */ for (;;) { int err = poll(pcmCopyHandle->pollFdsIn, pcmCopyHandle->nbPcmFdsIn, LOOP_TIMEOUT_MSEC); if (err < 0) { AFB_ApiError(pcmCopyHandle->api, "%s: poll err %s", __func__, strerror(errno)); continue; } if (err == 0) { /* timeout */ // AFB_ApiDebug(pcmCopyHandle->api, "%s(%s) alive, mute %d", __func__, pcmCopyHandle->pcmIn->cid.cardid, muted ); continue; } // handle the incoming events/mute order if ((eventFd->revents & POLLIN) != 0) { PcmCopyEvent event; size_t ret = read(eventFd->fd, &event, sizeof(event)); if (ret <= 0) continue; switch (event.eventType) { case PCM_COPY_MUTE: if (!muted) { readSuspend(pcmCopyHandle); muted = true; } break; case PCM_COPY_UNMUTE: if (muted) { readResume(pcmCopyHandle); muted = false; }; break; case PCM_COPY_END: AFB_ApiDebug(pcmCopyHandle->api, "%s ending -> EXIT", __func__); goto done; break; case PCM_COPY_LAST: default: AFB_ApiError(pcmCopyHandle->api, "%s: Unexpected event 0x%x", __func__, event.eventType); break; } continue; } unsigned short revents = 0; int res = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, framePfds, pcmCopyHandle->nbPcmFdsIn-1, &revents); if (res == -ENODEV) { sleep(1); continue; } if (revents & POLLHUP) { AFB_ApiNotice(pcmCopyHandle->api, "Frame POLLHUP"); continue; } if (!(revents & POLLIN)) { AFB_API_DEBUG(pcmCopyHandle->api, "no POLLIN. try again !"); continue; } AlsaPcmReadCB(pcmCopyHandle); } done: pthread_exit(0); return NULL; } static void *writeThreadEntry(void *handle) { AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; snd_pcm_t * pcmOut = pcmCopyHandle->pcmOut->handle; alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf; const char * cardid = pcmCopyHandle->pcmOut->cid.cardid; struct pollfd * eventFd = &pcmCopyHandle->pollFdsOut[0]; struct pollfd * framePfds = &pcmCopyHandle->pollFdsOut[1]; eventFd->events = POLLIN; const char * name = pcmCopyHandle->pcmOut->name; for (;;) { sem_wait(&pcmCopyHandle->sem); while (true) { if (pcmCopyHandle->ending) { AFB_ApiDebug(pcmCopyHandle->api, "%s: ending -> EXIT", __func__); goto done; } int err = poll(pcmCopyHandle->pollFdsOut, pcmCopyHandle->nbPcmFdsOut, LOOP_TIMEOUT_MSEC); if (err < 0) { AFB_API_ERROR(pcmCopyHandle->api, "%s: %s poll out err %s", __func__, name, strerror(errno)); continue; } // timeout if (err == 0) continue; // handle the incoming events/mute order if ((eventFd->revents & POLLIN) != 0) { PcmCopyEvent event; size_t ret = read(eventFd->fd, &event, sizeof(event)); if (ret <= 0) continue; switch (event.eventType) { case PCM_COPY_MUTE: case PCM_COPY_UNMUTE: break; case PCM_COPY_END: AFB_API_DEBUG(pcmCopyHandle->api, "%s ending -> EXIT", __func__); goto done; break; case PCM_COPY_LAST: default: AFB_API_ERROR(pcmCopyHandle->api, "%s: Unexpected event 0x%x", __func__, event.eventType); break; } continue; } unsigned short revents = 0; int res = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmOut->handle, framePfds, pcmCopyHandle->nbPcmFdsOut-1, &revents); if (res == -ENODEV) { sleep(1); continue; } if (revents & POLLHUP) { AFB_API_NOTICE(pcmCopyHandle->api, "Frame POLLHUP"); continue; } // not ready ... wait again if ((revents & POLLOUT) == 0) { // When that quirk is set, continuing to poll again would block too much time // and led to a EPIPE // Instead, we ignore the "non ready state" and perform the write directly if ((pcmCopyHandle->pcmOut->quirks & QUIRK_BOGUS_POLL_REVENTS_DEMANGLING) == 0) continue; } snd_pcm_sframes_t used, nbWritten; snd_pcm_sframes_t availOut = snd_pcm_avail(pcmOut); if (availOut < 0) { if (availOut == -EPIPE) { AFB_ApiDebug(pcmCopyHandle->api, "%s: write update EPIPE", cardid); xrun(pcmOut, (int)availOut); continue; } if (availOut == -ESTRPIPE) { AFB_ApiDebug(pcmCopyHandle->api, "%s: write update ESTRPIPE", cardid); suspend(pcmOut, (int)availOut); continue; } } pthread_mutex_lock(&pcmCopyHandle->mutex); used = alsa_ringbuf_frames_used(rbuf); if (used <= 0) { /* no more frames*/ pthread_mutex_unlock(&pcmCopyHandle->mutex); break; // will wait again } if (used > availOut) used = availOut; char buf[used*pcmCopyHandle->frame_size]; alsa_ringbuf_frames_pop(rbuf, buf, used); pthread_mutex_unlock(&pcmCopyHandle->mutex); nbWritten = snd_pcm_writei( pcmOut, buf, used); if (nbWritten <= 0) { if (nbWritten == -EPIPE) { int err = xrun(pcmOut, (int)nbWritten); AFB_ApiDebug(pcmCopyHandle->api, "XXX %s write EPIPE (%d), recov %d", pcmCopyHandle->pcmOut->cid.cardid, ++pcmCopyHandle->write_err_count , err); continue; } else if (nbWritten == -ESTRPIPE) { AFB_ApiDebug(pcmCopyHandle->api, "XXX write ESTRPIPE"); break; } else if (nbWritten == 0) { AFB_API_DEBUG(pcmCopyHandle->api, "%s: nothing was written", __func__); break; } AFB_ApiDebug(pcmCopyHandle->api, "%s: Unhandled error %s", __func__, strerror((int)-nbWritten)); break; } } } done: pthread_exit(0); return NULL; } PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute) { PcmCopyEvent event; event.eventType = mute?PCM_COPY_MUTE:PCM_COPY_UNMUTE; ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event)); (void) ret; return 0; } static int AlsaPcmCopyEndSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn) { PcmCopyEvent event; event.eventType = PCM_COPY_END; ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event)); (void) ret; return 0; } static int AlsaPcmCopyEnd(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) { handle->ending = true; // wake up the reader through the eventfd AlsaPcmCopyEndSignal(mixer, handle->pcmIn); // wake up the writer through the wait semaphore sem_post(&handle->sem); // and make in exit from poll AlsaPcmCopyEndSignal(mixer, handle->pcmOut); return 0; } PUBLIC int AlsaPcmCopyStop(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) { AFB_ApiDebug(mixer->api, "%s: Stopping copy threads of %s", __func__, handle->stream->uid); AlsaPcmCopyEnd(mixer, handle); if (pthread_join(handle->wthread, NULL) != 0) AFB_ApiDebug(mixer->api, "%s: Failed to join write thread", __func__); if (pthread_join(handle->rthread, NULL) != 0) AFB_ApiDebug(mixer->api, "%s: Failed to join read thread", __func__); AFB_ApiDebug(mixer->api, "%s: Copy threads of %s are STOPPED", __func__, handle->stream->uid); sem_destroy(&handle->sem); alsa_ringbuf_free(handle->rbuf); free(handle->pollFdsIn); free(handle->pollFdsOut); free(handle); return 0; } PUBLIC int AlsaPcmCopyStart(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) { char string[32]; int error; AlsaPcmCopyHandleT *cHandle=NULL; // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle); AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle); /* remember configuration of capture */ pcmIn->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT)); if (!pcmIn->params) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } memcpy(pcmIn->params, opts, sizeof(AlsaPcmHwInfoT)); pcmOut->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT)); if (!pcmOut->params) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } memcpy(pcmOut->params, opts, sizeof(AlsaPcmHwInfoT)); pcmIn->mixer = mixer; pcmOut->mixer = mixer; AFB_ApiDebug(mixer->api, "%s: Configure CAPTURE PCM", __func__); // prepare PCM for capture and replay error = AlsaPcmConf(mixer, pcmIn, SND_PCM_STREAM_CAPTURE); if (error) { AFB_ApiError(mixer->api, "%s: PCM configuration for capture failed", __func__); goto OnErrorExit; } AFB_ApiDebug(mixer->api, "%s: Configure PLAYBACK PCM", __func__); // input and output should match error = AlsaPcmConf(mixer, pcmOut, SND_PCM_STREAM_PLAYBACK); if (error) { AFB_ApiError(mixer->api, "%s: PCM configuration for playback failed", __func__); goto OnErrorExit; } // Prepare PCM for usage if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to prepare PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // Prepare PCM for usage if ((error = snd_pcm_prepare(pcmIn->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to prepare CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // Start PCM if ((error = snd_pcm_start(pcmOut->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to start PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; // Start PCM if ((error = snd_pcm_start(pcmIn->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to start CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; cHandle = calloc(1, sizeof(AlsaPcmCopyHandleT)); if (cHandle == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } cHandle->info = "pcmCpy"; cHandle->pcmIn = pcmIn; cHandle->pcmOut = pcmOut; cHandle->api = mixer->api; cHandle->channels = opts->channels; cHandle->stream = stream; cHandle->frame_size = (snd_pcm_format_physical_width(opts->format) / 8) * opts->channels; AFB_ApiDebug(mixer->api, "%s: Frame size is %zu (%d channels)", __func__, cHandle->frame_size, opts->channels); AFB_ApiDebug(mixer->api, "%s: Buffer delay is %d ms", __func__, stream->delayms); snd_pcm_uframes_t nbFrames = (stream->delayms * opts->rate)/1000; cHandle->rbuf = alsa_ringbuf_new(nbFrames, cHandle->frame_size); if (!cHandle->rbuf) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } cHandle->read_err_count = 0; cHandle->write_err_count = 0; AFB_ApiDebug(mixer->api, "%s Copy buffer: nbframes is %zu", __func__, nbFrames); // get FD poll descriptor for playback PCM int pcmOutCount = snd_pcm_poll_descriptors_count(pcmOut->handle); if (pcmOutCount <= 0) { AFB_API_ERROR(mixer->api, "%s: Fail pcmOut=%s get fds count error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; } cHandle->nbPcmFdsOut = pcmOutCount+1; cHandle->pollFdsOut = (struct pollfd *) malloc((cHandle->nbPcmFdsOut)*sizeof(struct pollfd)); if (cHandle->pollFdsOut == NULL){ SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } if ((error = snd_pcm_poll_descriptors(pcmOut->handle, cHandle->pollFdsOut+1, pcmOutCount)) < 0) { AFB_API_ERROR(mixer->api, "%s: Fail pcmOut=%s get pollfds error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // get FD poll descriptor for capture PCM int pcmInCount = snd_pcm_poll_descriptors_count(pcmIn->handle); if (pcmInCount <= 0) { AFB_ApiError(mixer->api, "%s: Fail pcmIn=%s get fds count error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; } cHandle->nbPcmFdsIn = pcmInCount+1; cHandle->pollFdsIn = (struct pollfd *) malloc((cHandle->nbPcmFdsIn)*sizeof(struct pollfd)); if (cHandle->pollFdsIn == NULL){ SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } if ((error = snd_pcm_poll_descriptors(pcmIn->handle, cHandle->pollFdsIn+1, pcmInCount)) < 0) { AFB_ApiError(mixer->api, "%s: Fail pcmIn=%s get pollfds error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // create the event pipe for capture int eventFdPipeIn[2]; error = pipe(eventFdPipeIn); if (error < 0) { AFB_ApiError(mixer->api, "Unable to create the mute signaling pipe for capture"); goto OnErrorExit; } struct pollfd * eventPollFdIn = &cHandle->pollFdsIn[0]; // read end eventPollFdIn->fd = eventFdPipeIn[0]; eventPollFdIn->events = POLLIN; eventPollFdIn->revents = 0; // write end pcmIn->eventFd = eventFdPipeIn[1]; // create the event pipe for playback int eventFdPipeOut[2]; error = pipe(eventFdPipeOut); if (error < 0) { AFB_API_ERROR(mixer->api, "Unable to create the signaling pipe for playback"); goto OnErrorExit; } struct pollfd * eventPollFdOut = &cHandle->pollFdsOut[0]; // read end eventPollFdOut->fd = eventFdPipeOut[0]; eventPollFdOut->events = POLLIN; eventPollFdOut->revents = 0; // write end pcmOut->eventFd = eventFdPipeOut[1]; error = sem_init(&cHandle->sem, 0 , 0); if (error < 0) { AFB_ApiError(mixer->api, "%s Fail to initialize the loop semaphore pcmIn=%s err=%d", __func__, ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); error = pthread_mutex_init(&cHandle->mutex, &attr); if (error < 0) { AFB_ApiError(mixer->api, "%s Fail initialize loop mutex pcmIn=%s err=%d", __func__, ALSA_PCM_UID(pcmIn->handle, string), error); } /// start a thread for writing if ((error = pthread_create(&cHandle->wthread, NULL, &readThreadEntry, cHandle)) < 0) { AFB_ApiError(mixer->api, "%s Fail create write thread pcmOut=%s err=%d", __func__, ALSA_PCM_UID(pcmOut->handle, string), error); goto OnErrorExit; } // start a thread for reading if ((error = pthread_create(&cHandle->rthread, NULL, &writeThreadEntry, cHandle)) < 0) { AFB_ApiError(mixer->api, "%s Fail create read thread pcmIn=%s err=%d", __func__, ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } // request a higher priority for each audio stream thread struct sched_param params; params.sched_priority = sched_get_priority_max(SCHED_FIFO); error= pthread_setschedparam(cHandle->rthread, SCHED_FIFO, ¶ms); if (error) { AFB_ApiWarning(mixer->api, "%s: Failed to increase stream read thread priority pcmIn=%s err=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), strerror(error)); } error= pthread_setschedparam(cHandle->wthread, SCHED_FIFO, ¶ms); if (error) { AFB_ApiWarning(mixer->api, "%s: Failed to increase stream write thread priority pcmIn=%s err=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), strerror(error)); } stream->copy = cHandle; return 0; OnErrorExit: AFB_ApiError(mixer->api, "%s: - pcmIn=%s" , __func__, ALSA_PCM_UID(pcmIn->handle, string)); AFB_ApiError(mixer->api, "%s: - pcmOut=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string)); if (cHandle &&cHandle->pollFdsIn) { free (cHandle->pollFdsIn); cHandle->pollFdsIn = NULL; } if (cHandle && cHandle->pollFdsOut) { free (cHandle->pollFdsOut); cHandle->pollFdsOut = NULL; } if (eventFdPipeIn[0]) close(eventFdPipeIn[0]); if (eventFdPipeIn[1]) close(eventFdPipeIn[1]); if (eventFdPipeOut[0]) close(eventFdPipeOut[0]); if (eventFdPipeOut[1]) close(eventFdPipeOut[1]); if (cHandle) free(cHandle); return -1; }