예제 #1
0
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;
}
예제 #2
0
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;
}
예제 #3
0
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;
}
예제 #4
0
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;
}
예제 #5
0
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;
}