Beispiel #1
0
int
writePcmData (PcmDevice *pcm, const unsigned char *buffer, int count) {
  int frameSize = getPcmFrameSize(pcm);
  int framesLeft = count / frameSize;

  while (framesLeft > 0) {
    int result;

    if ((result = pcmAlsa_pcm_writei(pcm->handle, buffer, framesLeft)) > 0) {
      framesLeft -= result;
      buffer += result * frameSize;
    } else {
      switch (result) {
        case -EPIPE:
          if ((result = pcmAlsa_pcm_prepare(pcm->handle)) < 0) {
            logPcmError(LOG_WARNING, "underrun recovery - prepare", result);
            return 0;
          }
          continue;

        case -ESTRPIPE:
          while ((result = pcmAlsa_pcm_resume(pcm->handle)) == -EAGAIN) approximateDelay(1);

          if (result < 0) {
            if ((result = pcmAlsa_pcm_prepare(pcm->handle)) < 0) {
              logPcmError(LOG_WARNING, "resume - prepare", result);
              return 0;
            }
          }
          continue;
      }
    }
  }
  return 1;
}
Beispiel #2
0
static int
configurePcmChannelCount (PcmDevice *pcm, int errorLevel) {
  int result;
  unsigned int minimum;
  unsigned int maximum;

  if ((result = pcmAlsa_pcm_hw_params_get_channels_min(pcm->hardwareParameters, &minimum)) < 0) {
    logPcmError(errorLevel, "get channels min", result);
    return 0;
  }

  if ((result = pcmAlsa_pcm_hw_params_get_channels_max(pcm->hardwareParameters, &maximum)) < 0) {
    logPcmError(errorLevel, "get channels max", result);
    return 0;
  }

  if ((minimum > maximum) || (minimum < 1)) {
    logMessage(errorLevel, "Invalid PCM channel range: %u-%u", minimum, maximum);
    return 0;
  }

  pcm->channelCount = minimum;
  if ((result = pcmAlsa_pcm_hw_params_set_channels_near(pcm->handle, pcm->hardwareParameters, &pcm->channelCount)) < 0) {
    logPcmError(errorLevel, "set channels near", result);
    return 0;
  }

  return 1;
}
Beispiel #3
0
static int
configurePcmSampleRate (PcmDevice *pcm, int errorLevel) {
  int result;
  unsigned int minimum;
  unsigned int maximum;

  if ((result = pcmAlsa_pcm_hw_params_get_rate_min(pcm->hardwareParameters, &minimum, NULL)) < 0) {
    logPcmError(errorLevel, "get rate min", result);
    return 0;
  }

  if ((result = pcmAlsa_pcm_hw_params_get_rate_max(pcm->hardwareParameters, &maximum, NULL)) < 0) {
    logPcmError(errorLevel, "get rate max", result);
    return 0;
  }

  if ((minimum > maximum) || (minimum < 1)) {
    logMessage(errorLevel, "Invalid PCM rate range: %u-%u", minimum, maximum);
    return 0;
  }

  pcm->sampleRate = MIN(MAX(16000, minimum), maximum);
  if ((result = pcmAlsa_pcm_hw_params_set_rate_near(pcm->handle, pcm->hardwareParameters, &pcm->sampleRate, NULL)) < 0) {
    logPcmError(errorLevel, "set rate near", result);
    return 0;
  }

  return 1;
}
Beispiel #4
0
static int
configurePcmSampleFormat (PcmDevice *pcm, int errorLevel) {
  static const snd_pcm_format_t formats[] = {
    SND_PCM_FORMAT_S16, SND_PCM_FORMAT_U16,
    SND_PCM_FORMAT_U8, SND_PCM_FORMAT_S8,
    SND_PCM_FORMAT_MU_LAW,
    SND_PCM_FORMAT_UNKNOWN
  };
  const snd_pcm_format_t *format = formats;

  while (*format != SND_PCM_FORMAT_UNKNOWN) {
    int result = pcmAlsa_pcm_hw_params_set_format(pcm->handle, pcm->hardwareParameters, *format);
    if (result >= 0) return 1;

    if (result != -EINVAL) {
      logPcmError(errorLevel, "set format", result);
      return 0;
    }

    ++format;
  }

  logMessage(errorLevel, "Unsupported PCM sample format.");
  return 0;
}
Beispiel #5
0
void
awaitPcmOutput (PcmDevice *pcm) {
    int code;
    if ((code = snd_pcm_playback_flush(pcm->handle)) < 0) {
        logPcmError(LOG_WARNING, "flush", code);
    }
}
Beispiel #6
0
void
cancelPcmOutput (PcmDevice *pcm) {
    int code;
    if ((code = snd_pcm_playback_drain(pcm->handle)) < 0) {
        logPcmError(LOG_WARNING, "drain", code);
    }
}
Beispiel #7
0
void
closePcmDevice (PcmDevice *pcm) {
    int code;
    if ((code = snd_pcm_close(pcm->handle)) < 0) {
        logPcmError(LOG_WARNING, "close", code);
    }

    free(pcm);
}
Beispiel #8
0
int
setPcmChannelCount (PcmDevice *pcm, int channels) {
  int result;

  pcm->channelCount = channels;
  if ((result = pcmAlsa_pcm_hw_params_set_channels_near(pcm->handle, pcm->hardwareParameters, &pcm->channelCount)) < 0) {
    logPcmError(LOG_ERR, "set channels near", result);
  }

  return getPcmChannelCount(pcm);
}
Beispiel #9
0
int
setPcmSampleRate (PcmDevice *pcm, int rate) {
  int result;

  pcm->sampleRate = rate;
  if ((result = pcmAlsa_pcm_hw_params_set_rate_near(pcm->handle, pcm->hardwareParameters, &pcm->sampleRate, NULL)) < 0) {
    logPcmError(LOG_ERR, "set rate near", result);
  }

  return getPcmSampleRate(pcm);
}
Beispiel #10
0
int
getPcmBlockSize (PcmDevice *pcm) {
  snd_pcm_uframes_t frames;
  int result;

  if ((result = pcmAlsa_pcm_hw_params_get_period_size(pcm->hardwareParameters, &frames, NULL)) >= 0) {
    return frames * getPcmFrameSize(pcm);
  } else {
    logPcmError(LOG_ERR, "get period size", result);
  }
  return 65535;
}
Beispiel #11
0
static int
reconfigurePcmChannel (PcmDevice *pcm, int errorLevel) {
    int code;
    if ((code = snd_pcm_channel_params(pcm->handle, &pcm->parameters)) >= 0) {
        snd_pcm_channel_setup_t setup;
        setup.channel = pcm->parameters.channel;
        if ((code = snd_pcm_channel_setup(pcm->handle, &setup)) >= 0) {
            pcm->parameters.mode = setup.mode;
            pcm->parameters.format = setup.format;
            pcm->parameters.buf.block.frag_size = setup.buf.block.frag_size;
            pcm->parameters.buf.block.frags_min = setup.buf.block.frags_min;
            pcm->parameters.buf.block.frags_max = setup.buf.block.frags_max;
            return 1;
        } else {
            logPcmError(errorLevel, "get channel setup", code);
        }
    } else {
        logPcmError(errorLevel, "set channel parameters", code);
    }
    return 0;
}
Beispiel #12
0
PcmAmplitudeFormat
getPcmAmplitudeFormat (PcmDevice *pcm) {
  snd_pcm_format_t format;
  int result;

  if ((result = pcmAlsa_pcm_hw_params_get_format(pcm->hardwareParameters, &format)) < 0) {
    logPcmError(LOG_ERR, "get format", result);
  } else {
    const AmplitudeFormatEntry *entry = amplitudeFormatTable;
    while (entry->internal != PCM_FMT_UNKNOWN) {
      if (entry->external == format) return entry->internal;
      ++entry;
    }
  }
  return PCM_FMT_UNKNOWN;
}
Beispiel #13
0
PcmAmplitudeFormat
setPcmAmplitudeFormat (PcmDevice *pcm, PcmAmplitudeFormat format) {
  const AmplitudeFormatEntry *entry = amplitudeFormatTable;
  int result;

  while (entry->internal != PCM_FMT_UNKNOWN) {
    if (entry->internal == format) break;
    ++entry;
  }

  if ((result = pcmAlsa_pcm_hw_params_set_format(pcm->handle, pcm->hardwareParameters, entry->external)) < 0) {
    logPcmError(LOG_ERR, "set format", result);
    return getPcmAmplitudeFormat(pcm);
  }

  return entry->internal;
}
Beispiel #14
0
PcmDevice *
openPcmDevice (int errorLevel, const char *device) {
    PcmDevice *pcm;
    if ((pcm = malloc(sizeof(*pcm)))) {
        int code;

        if (*device) {
            {
                int ok = 0;
                long number;
                char *end;
                const char *component = device;

                number = strtol(component, &end, 0);
                if ((*end && (*end != ':')) || (number < 0) || (number > 0XFF)) {
                    logMessage(errorLevel, "Invalid QSA card number: %s", device);
                } else if (end == component) {
                    logMessage(errorLevel, "Missing QSA card number: %s", device);
                } else {
                    pcm->card = number;

                    if (*end) {
                        component = end + 1;
                        number = strtol(component, &end, 0);
                        if (*end || (number < 0) || (number > 0XFF)) {
                            logMessage(errorLevel, "Invalid QSA device number: %s", device);
                        } else if (end == component) {
                            logMessage(errorLevel, "Missing QSA device number: %s", device);
                        } else {
                            pcm->device = number;
                            ok = 1;
                        }
                    } else {
                        pcm->device = 0;
                        ok = 1;
                    }
                }

                if (!ok) goto openError;
            }

            if ((code = snd_pcm_open(&pcm->handle, pcm->card, pcm->device, SND_PCM_OPEN_PLAYBACK)) < 0) {
                logPcmError(errorLevel, "open", code);
                goto openError;
            }
        } else if ((code = snd_pcm_open_preferred(&pcm->handle, &pcm->card, &pcm->device, SND_PCM_OPEN_PLAYBACK)) < 0) {
            logPcmError(errorLevel, "preferred open", code);
            goto openError;
        }
        logMessage(LOG_DEBUG, "QSA PCM device opened: %d:%d", pcm->card, pcm->device);

        {
            snd_pcm_channel_info_t info;
            info.channel = SND_PCM_CHANNEL_PLAYBACK;
            if ((code = snd_pcm_channel_info(pcm->handle, &info)) >= 0) {
                logMessage(LOG_DEBUG, "QSA PCM Info: Frag=%d-%d Rate=%d-%d Chan=%d-%d",
                           info.min_fragment_size, info.max_fragment_size,
                           info.min_rate, info.max_rate,
                           info.min_voices, info.max_voices);
                memset(&pcm->parameters, 0, sizeof(pcm->parameters));

                pcm->parameters.channel = info.channel;
                pcm->parameters.start_mode = SND_PCM_START_DATA;
                pcm->parameters.stop_mode = SND_PCM_STOP_ROLLOVER;

                switch (pcm->parameters.mode = SND_PCM_MODE_BLOCK) {
                case SND_PCM_MODE_BLOCK:
                    pcm->parameters.buf.block.frag_size = MIN(MAX(0X400, info.min_fragment_size), info.max_fragment_size);
                    pcm->parameters.buf.block.frags_min = 1;
                    pcm->parameters.buf.block.frags_max = 0X40;
                    break;

                default:
                    logMessage(LOG_WARNING, "Unsupported QSA PCM mode: %d", pcm->parameters.mode);
                    goto openError;
                }

                pcm->parameters.format.interleave = 1;
                pcm->parameters.format.rate = info.max_rate;
                pcm->parameters.format.voices = MIN(MAX(1, info.min_voices), info.max_voices);
                pcm->parameters.format.format = SND_PCM_SFMT_S16;

                if (reconfigurePcmChannel(pcm, errorLevel)) {
                    if ((code = snd_pcm_channel_prepare(pcm->handle, pcm->parameters.channel)) >= 0) {
                        return pcm;
                    } else {
                        logPcmError(errorLevel, "prepare channel", code);
                    }
                }
            } else {
                logPcmError(errorLevel, "get channel information", code);
            }
        }

openError:
        free(pcm);
    } else {
        logSystemError("PCM device allocation");
    }

    return NULL;
}
Beispiel #15
0
void
awaitPcmOutput (PcmDevice *pcm) {
  int result;
  if ((result = pcmAlsa_pcm_drain(pcm->handle)) < 0) logPcmError(LOG_WARNING, "drain", result);
}
Beispiel #16
0
PcmDevice *
openPcmDevice (int errorLevel, const char *device) {
  PcmDevice *pcm;

  if (!pcmAlsaLibrary) {
    if (!(pcmAlsaLibrary = loadSharedObject("libasound.so.2"))) {
      logMessage(LOG_ERR, "Unable to load ALSA PCM library.");
      return NULL;
    }

    PCM_ALSA_LOCATE(strerror);
    PCM_ALSA_LOCATE(pcm_open);
    PCM_ALSA_LOCATE(pcm_close);
    PCM_ALSA_LOCATE(pcm_nonblock);
    PCM_ALSA_LOCATE(pcm_hw_params_malloc);
    PCM_ALSA_LOCATE(pcm_hw_params_any);
    PCM_ALSA_LOCATE(pcm_hw_params_set_access);
    PCM_ALSA_LOCATE(pcm_hw_params_get_channels);
    PCM_ALSA_LOCATE(pcm_hw_params_get_channels_min);
    PCM_ALSA_LOCATE(pcm_hw_params_get_channels_max);
    PCM_ALSA_LOCATE(pcm_hw_params_set_channels_near);
    PCM_ALSA_LOCATE(pcm_hw_params_get_format);
    PCM_ALSA_LOCATE(pcm_hw_params_set_format);
    PCM_ALSA_LOCATE(pcm_hw_params_get_rate);
    PCM_ALSA_LOCATE(pcm_hw_params_get_sbits);
    PCM_ALSA_LOCATE(pcm_hw_params_get_rate_min);
    PCM_ALSA_LOCATE(pcm_hw_params_get_rate_max);
    PCM_ALSA_LOCATE(pcm_hw_params_get_period_size);
    PCM_ALSA_LOCATE(pcm_hw_params_set_rate_near);
    PCM_ALSA_LOCATE(pcm_hw_params_set_buffer_time_near);
    PCM_ALSA_LOCATE(pcm_hw_params_set_period_time_near);
    PCM_ALSA_LOCATE(pcm_hw_params);
    PCM_ALSA_LOCATE(pcm_hw_params_get_sbits);
    PCM_ALSA_LOCATE(pcm_hw_params_free);
    PCM_ALSA_LOCATE(pcm_prepare);
    PCM_ALSA_LOCATE(pcm_writei);
    PCM_ALSA_LOCATE(pcm_drain);
    PCM_ALSA_LOCATE(pcm_drop);
    PCM_ALSA_LOCATE(pcm_resume);
  }

  if ((pcm = malloc(sizeof(*pcm)))) {
    int result;

    if (!*device) device = "default";
    if ((result = pcmAlsa_pcm_open(&pcm->handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0) {
      pcmAlsa_pcm_nonblock(pcm->handle, 0);

      if ((result = pcmAlsa_pcm_hw_params_malloc(&pcm->hardwareParameters)) >= 0) {
        if ((result = pcmAlsa_pcm_hw_params_any(pcm->handle, pcm->hardwareParameters)) >= 0) {
          if ((result = pcmAlsa_pcm_hw_params_set_access(pcm->handle, pcm->hardwareParameters, SND_PCM_ACCESS_RW_INTERLEAVED)) >= 0) {
            if (configurePcmSampleFormat(pcm, errorLevel)) {
              if (configurePcmSampleRate(pcm, errorLevel)) {
                if (configurePcmChannelCount(pcm, errorLevel)) {
                  pcm->bufferTime = 500000;
                  if ((result = pcmAlsa_pcm_hw_params_set_buffer_time_near(pcm->handle, pcm->hardwareParameters, &pcm->bufferTime, NULL)) >= 0) {
                    pcm->periodTime = pcm->bufferTime / 8;
                    if ((result = pcmAlsa_pcm_hw_params_set_period_time_near(pcm->handle, pcm->hardwareParameters, &pcm->periodTime, NULL)) >= 0) {
                      if ((result = pcmAlsa_pcm_hw_params(pcm->handle, pcm->hardwareParameters)) >= 0) {
                        logMessage(LOG_DEBUG, "ALSA PCM: Chan=%u Rate=%u BufTim=%u PerTim=%u", pcm->channelCount, pcm->sampleRate, pcm->bufferTime, pcm->periodTime);
                        return pcm;
                      } else {
                        logPcmError(errorLevel, "set hardware parameters", result);
                      }
                    } else {
                      logPcmError(errorLevel, "set period time near", result);
                    }
                  } else {
                    logPcmError(errorLevel, "set buffer time near", result);
                  }
                }
              }
            }
          } else {
            logPcmError(errorLevel, "set access", result);
          }
        } else {
          logPcmError(errorLevel, "get hardware parameters", result);
        }

        pcmAlsa_pcm_hw_params_free(pcm->hardwareParameters);
      } else {
        logPcmError(errorLevel, "hardware parameters allocation", result);
      }

      pcmAlsa_pcm_close(pcm->handle);
    } else {
      logPcmError(errorLevel, "open", result);
    }

    free(pcm);
  } else {
    logSystemError("PCM device allocation");
  }

  return NULL;
}
Beispiel #17
0
void
cancelPcmOutput (PcmDevice *pcm) {
  int result;
  if ((result = pcmAlsa_pcm_drop(pcm->handle)) < 0) logPcmError(LOG_WARNING, "drop", result);
}