bool CAESinkALSA::InitializeHW(const ALSAConfig &inconfig, ALSAConfig &outconfig) { snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); memset(hw_params, 0, snd_pcm_hw_params_sizeof()); snd_pcm_hw_params_any(m_pcm, hw_params); snd_pcm_hw_params_set_access(m_pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); unsigned int sampleRate = inconfig.sampleRate; unsigned int channelCount = inconfig.channels; snd_pcm_hw_params_set_rate_near (m_pcm, hw_params, &sampleRate, NULL); snd_pcm_hw_params_set_channels_near(m_pcm, hw_params, &channelCount); /* ensure we opened X channels or more */ if (inconfig.channels > channelCount) { CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Unable to open the required number of channels"); } /* update outconfig */ outconfig.channels = channelCount; snd_pcm_format_t fmt = AEFormatToALSAFormat(inconfig.format); outconfig.format = inconfig.format; if (fmt == SND_PCM_FORMAT_UNKNOWN) { /* if we dont support the requested format, fallback to float */ fmt = SND_PCM_FORMAT_FLOAT; outconfig.format = AE_FMT_FLOAT; } /* try the data format */ if (snd_pcm_hw_params_set_format(m_pcm, hw_params, fmt) < 0) { /* if the chosen format is not supported, try each one in decending order */ CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Your hardware does not support %s, trying other formats", CAEUtil::DataFormatToStr(outconfig.format)); for (enum AEDataFormat i = AE_FMT_MAX; i > AE_FMT_INVALID; i = (enum AEDataFormat)((int)i - 1)) { if (AE_IS_RAW(i) || i == AE_FMT_MAX) continue; if (m_passthrough && i != AE_FMT_S16BE && i != AE_FMT_S16LE) continue; fmt = AEFormatToALSAFormat(i); if (fmt == SND_PCM_FORMAT_UNKNOWN || snd_pcm_hw_params_set_format(m_pcm, hw_params, fmt) < 0) { fmt = SND_PCM_FORMAT_UNKNOWN; continue; } int fmtBits = CAEUtil::DataFormatToBits(i); int bits = snd_pcm_hw_params_get_sbits(hw_params); if (bits != fmtBits) { /* if we opened in 32bit and only have 24bits, pack into 24 */ if (fmtBits == 32 && bits == 24) i = AE_FMT_S24NE4; else continue; } /* record that the format fell back to X */ outconfig.format = i; CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Using data format %s", CAEUtil::DataFormatToStr(outconfig.format)); break; } /* if we failed to find a valid output format */ if (fmt == SND_PCM_FORMAT_UNKNOWN) { CLog::Log(LOGERROR, "CAESinkALSA::InitializeHW - Unable to find a suitable output format"); return false; } } snd_pcm_uframes_t periodSize, bufferSize; snd_pcm_hw_params_get_buffer_size_max(hw_params, &bufferSize); snd_pcm_hw_params_get_period_size_max(hw_params, &periodSize, NULL); /* We want to make sure, that we have max 200 ms Buffer with a periodSize of approx 50 ms. Choosing a higher bufferSize will cause problems with menu sounds. Buffer will be increased after those are fixed. */ periodSize = std::min(periodSize, (snd_pcm_uframes_t) sampleRate / 20); bufferSize = std::min(bufferSize, (snd_pcm_uframes_t) sampleRate / 5); /* According to upstream we should set buffer size first - so make sure it is always at least 4x period size to not get underruns (some systems seem to have issues with only 2 periods) */ periodSize = std::min(periodSize, bufferSize / 4); CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Request: periodSize %lu, bufferSize %lu", periodSize, bufferSize); snd_pcm_hw_params_t *hw_params_copy; snd_pcm_hw_params_alloca(&hw_params_copy); snd_pcm_hw_params_copy(hw_params_copy, hw_params); // copy what we have and is already working // Make sure to not initialize too large to not cause underruns snd_pcm_uframes_t periodSizeMax = bufferSize / 3; if(snd_pcm_hw_params_set_period_size_max(m_pcm, hw_params_copy, &periodSizeMax, NULL) != 0) { snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Request: Failed to limit periodSize to %lu", periodSizeMax); } // first trying bufferSize, PeriodSize // for more info see here: // http://mailman.alsa-project.org/pipermail/alsa-devel/2009-September/021069.html // the last three tries are done as within pulseaudio // backup periodSize and bufferSize first. Restore them after every failed try snd_pcm_uframes_t periodSizeTemp, bufferSizeTemp; periodSizeTemp = periodSize; bufferSizeTemp = bufferSize; if (snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize) != 0 || snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL) != 0 || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0) { bufferSize = bufferSizeTemp; periodSize = periodSizeTemp; // retry with PeriodSize, bufferSize snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy if (snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL) != 0 || snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize) != 0 || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0) { // try only periodSize periodSize = periodSizeTemp; snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restore working copy if(snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL) != 0 || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0) { // try only BufferSize bufferSize = bufferSizeTemp; snd_pcm_hw_params_copy(hw_params_copy, hw_params); // restory working copy if (snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize) != 0 || snd_pcm_hw_params(m_pcm, hw_params_copy) != 0) { // set default that Alsa would choose CLog::Log(LOGWARNING, "CAESinkAlsa::IntializeHW - Using default alsa values - set failed"); if (snd_pcm_hw_params(m_pcm, hw_params) != 0) { CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Could not init a valid sink"); return false; } } } // reread values when alsa default was kept snd_pcm_get_params(m_pcm, &bufferSize, &periodSize); } } CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Got: periodSize %lu, bufferSize %lu", periodSize, bufferSize); /* set the format parameters */ outconfig.sampleRate = sampleRate; outconfig.periodSize = periodSize; outconfig.frameSize = snd_pcm_frames_to_bytes(m_pcm, 1); m_bufferSize = (unsigned int)bufferSize; m_timeout = std::ceil((double)(bufferSize * 1000) / (double)sampleRate); CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Setting timeout to %d ms", m_timeout); return true; }
status_t setSoftwareParams(alsa_handle_t *handle) { snd_pcm_sw_params_t * softwareParams; int err; snd_pcm_uframes_t bufferSize = 0; snd_pcm_uframes_t periodSize = 0; snd_pcm_uframes_t startThreshold, stopThreshold; if (snd_pcm_sw_params_malloc(&softwareParams) < 0) { LOG_ALWAYS_FATAL("Failed to allocate ALSA software parameters!"); return NO_INIT; } // Get the current software parameters err = snd_pcm_sw_params_current(handle->handle, softwareParams); if (err < 0) { ALOGE("Unable to get software parameters: %s", snd_strerror(err)); goto done; } // Configure ALSA to start the transfer when the buffer is almost full. snd_pcm_get_params(handle->handle, &bufferSize, &periodSize); if (handle->devices & AudioSystem::DEVICE_OUT_ALL) { // For playback, configure ALSA to start the transfer when the // buffer is full. startThreshold = bufferSize - 1; stopThreshold = bufferSize; } else { // For recording, configure ALSA to start the transfer on the // first frame. startThreshold = 1; stopThreshold = bufferSize; } err = snd_pcm_sw_params_set_start_threshold(handle->handle, softwareParams, startThreshold); if (err < 0) { ALOGE("Unable to set start threshold to %lu frames: %s", startThreshold, snd_strerror(err)); goto done; } err = snd_pcm_sw_params_set_stop_threshold(handle->handle, softwareParams, stopThreshold); if (err < 0) { ALOGE("Unable to set stop threshold to %lu frames: %s", stopThreshold, snd_strerror(err)); goto done; } // Allow the transfer to start when at least periodSize samples can be // processed. err = snd_pcm_sw_params_set_avail_min(handle->handle, softwareParams, periodSize); if (err < 0) { ALOGE("Unable to configure available minimum to %lu: %s", periodSize, snd_strerror(err)); goto done; } // Commit the software parameters back to the device. err = snd_pcm_sw_params(handle->handle, softwareParams); if (err < 0) ALOGE("Unable to configure software parameters: %s", snd_strerror(err)); done: snd_pcm_sw_params_free(softwareParams); return err; }
bool S9xAlsaSoundDriver::open_device() { int err; unsigned int periods = 8; unsigned int buffer_size = gui_config->sound_buffer_size * 1000; snd_pcm_sw_params_t *sw_params; snd_pcm_hw_params_t *hw_params; snd_pcm_uframes_t alsa_buffer_size, alsa_period_size; unsigned int min = 0; unsigned int max = 0; printf("ALSA sound driver initializing...\n"); printf(" --> (Device: default)...\n"); err = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) { goto fail; } printf(" --> (16-bit Stereo, %dhz, %d ms)...\n", Settings.SoundPlaybackRate, gui_config->sound_buffer_size); snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_any(pcm, hw_params); snd_pcm_hw_params_set_format(pcm, hw_params, SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_rate_resample(pcm, hw_params, 0); snd_pcm_hw_params_set_channels(pcm, hw_params, 2); snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL); snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL); printf(" --> Available rates: %d to %d\n", min, max); if (Settings.SoundPlaybackRate > max && Settings.SoundPlaybackRate < min) { printf(" Rate %d not available. Using %d instead.\n", Settings.SoundPlaybackRate, max); Settings.SoundPlaybackRate = max; } snd_pcm_hw_params_set_rate_near(pcm, hw_params, &Settings.SoundPlaybackRate, NULL); snd_pcm_hw_params_get_buffer_time_min(hw_params, &min, NULL); snd_pcm_hw_params_get_buffer_time_max(hw_params, &max, NULL); printf(" --> Available buffer sizes: %dms to %dms\n", min / 1000, max / 1000); if (buffer_size < min && buffer_size > max) { printf(" Buffer size %dms not available. Using %d instead.\n", buffer_size / 1000, (min + max) / 2000); buffer_size = (min + max) / 2; } snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_size, NULL); snd_pcm_hw_params_get_periods_min(hw_params, &min, NULL); snd_pcm_hw_params_get_periods_max(hw_params, &max, NULL); printf(" --> Period ranges: %d to %d blocks\n", min, max); if (periods > max) { periods = max; } snd_pcm_hw_params_set_periods_near(pcm, hw_params, &periods, NULL); if ((err = snd_pcm_hw_params(pcm, hw_params)) < 0) { printf(" Hardware parameter set failed.\n"); goto close_fail; } snd_pcm_sw_params_alloca(&sw_params); snd_pcm_sw_params_current(pcm, sw_params); snd_pcm_get_params(pcm, &alsa_buffer_size, &alsa_period_size); /* Don't start until we're [nearly] full */ snd_pcm_sw_params_set_start_threshold(pcm, sw_params, (alsa_buffer_size / 2)); err = snd_pcm_sw_params(pcm, sw_params); output_buffer_size = snd_pcm_frames_to_bytes(pcm, alsa_buffer_size); if (err < 0) { printf(" Software parameter set failed.\n"); goto close_fail; } printf("OK\n"); S9xSetSamplesAvailableCallback(alsa_samples_available, this); return true; close_fail: snd_pcm_drain(pcm); snd_pcm_close(pcm); pcm = NULL; fail: printf("Failed: %s\n", snd_strerror(err)); return false; }
static int alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, cubeb_stream_params stream_params, unsigned int latency, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { cubeb_stream * stm; int r; snd_pcm_format_t format; assert(ctx && stream); *stream = NULL; switch (stream_params.format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; case CUBEB_SAMPLE_S16BE: format = SND_PCM_FORMAT_S16_BE; break; case CUBEB_SAMPLE_FLOAT32LE: format = SND_PCM_FORMAT_FLOAT_LE; break; case CUBEB_SAMPLE_FLOAT32BE: format = SND_PCM_FORMAT_FLOAT_BE; break; default: return CUBEB_ERROR_INVALID_FORMAT; } pthread_mutex_lock(&ctx->mutex); if (ctx->active_streams >= CUBEB_STREAM_MAX) { pthread_mutex_unlock(&ctx->mutex); return CUBEB_ERROR; } ctx->active_streams += 1; pthread_mutex_unlock(&ctx->mutex); stm = calloc(1, sizeof(*stm)); assert(stm); stm->context = ctx; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->params = stream_params; stm->state = INACTIVE; stm->volume = 1.0; r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } r = snd_pcm_nonblock(stm->pcm, 1); assert(r == 0); /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274. Only resort to this hack if the handle_underrun workaround failed. */ if (!ctx->local_config && ctx->is_pa) { latency = latency < 500 ? 500 : latency; } r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, stm->params.channels, stm->params.rate, 1, latency * 1000); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR_INVALID_FORMAT; } r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &stm->period_size); assert(r == 0); stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); assert(stm->nfds > 0); stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); assert(stm->saved_fds); r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds); assert((nfds_t) r == stm->nfds); r = pthread_cond_init(&stm->cond, NULL); assert(r == 0); if (alsa_register_stream(ctx, stm) != 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } *stream = stm; return CUBEB_OK; }
static int alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, unsigned int latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { (void)stream_name; cubeb_stream * stm; int r; snd_pcm_format_t format; snd_pcm_uframes_t period_size; int latency_us = 0; assert(ctx && stream); if (input_stream_params) { /* Capture support not yet implemented. */ return CUBEB_ERROR_NOT_SUPPORTED; } if (input_device || output_device) { /* Device selection not yet implemented. */ return CUBEB_ERROR_DEVICE_UNAVAILABLE; } *stream = NULL; switch (output_stream_params->format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; case CUBEB_SAMPLE_S16BE: format = SND_PCM_FORMAT_S16_BE; break; case CUBEB_SAMPLE_FLOAT32LE: format = SND_PCM_FORMAT_FLOAT_LE; break; case CUBEB_SAMPLE_FLOAT32BE: format = SND_PCM_FORMAT_FLOAT_BE; break; default: return CUBEB_ERROR_INVALID_FORMAT; } pthread_mutex_lock(&ctx->mutex); if (ctx->active_streams >= CUBEB_STREAM_MAX) { pthread_mutex_unlock(&ctx->mutex); return CUBEB_ERROR; } ctx->active_streams += 1; pthread_mutex_unlock(&ctx->mutex); stm = calloc(1, sizeof(*stm)); assert(stm); stm->context = ctx; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->params = *output_stream_params; stm->state = INACTIVE; stm->volume = 1.0; r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } r = snd_pcm_nonblock(stm->pcm, 1); assert(r == 0); latency_us = latency_frames * 1e6 / stm->params.rate; /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274. Only resort to this hack if the handle_underrun workaround failed. */ if (!ctx->local_config && ctx->is_pa) { const int min_latency = 5e5; latency_us = latency_us < min_latency ? min_latency: latency_us; } r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, stm->params.channels, stm->params.rate, 1, latency_us); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR_INVALID_FORMAT; } r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); assert(r == 0); stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); assert(stm->nfds > 0); stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); assert(stm->saved_fds); r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds); assert((nfds_t) r == stm->nfds); r = pthread_cond_init(&stm->cond, NULL); assert(r == 0); if (alsa_register_stream(ctx, stm) != 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } *stream = stm; return CUBEB_OK; }