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; }
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; }
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; }
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; }
void awaitPcmOutput (PcmDevice *pcm) { int code; if ((code = snd_pcm_playback_flush(pcm->handle)) < 0) { logPcmError(LOG_WARNING, "flush", code); } }
void cancelPcmOutput (PcmDevice *pcm) { int code; if ((code = snd_pcm_playback_drain(pcm->handle)) < 0) { logPcmError(LOG_WARNING, "drain", code); } }
void closePcmDevice (PcmDevice *pcm) { int code; if ((code = snd_pcm_close(pcm->handle)) < 0) { logPcmError(LOG_WARNING, "close", code); } free(pcm); }
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); }
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); }
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; }
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; }
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; }
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; }
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; }
void awaitPcmOutput (PcmDevice *pcm) { int result; if ((result = pcmAlsa_pcm_drain(pcm->handle)) < 0) logPcmError(LOG_WARNING, "drain", result); }
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; }
void cancelPcmOutput (PcmDevice *pcm) { int result; if ((result = pcmAlsa_pcm_drop(pcm->handle)) < 0) logPcmError(LOG_WARNING, "drop", result); }