#define SND_MSGUNDER typedef struct { snd_pcm_t *handle; void *buffer; int *conv; uint samplerate; uint blocksize; uint channels; uint width; uint smppos; uint devbufsize; #ifdef SND_MSGUNDER uint underruns; uint lastunder; uint pushed; uint pad; #else uint pad[4]; #endif } aud_handle; void aud_prepare(aud_handle *dev) { if(dev->handle == NULL) { return; } memset(dev->buffer, 0, dev->blocksize * dev->width * dev->channels); dev->smppos = 0; int rc = 0; uint tries = 0; while(rc != dev->blocksize) { if(tries++ >= 256) { snd_logw(STR_AUD_PREPERR); return; } rc = snd_pcm_writei(dev->handle, dev->buffer, dev->blocksize); if(rc == -EAGAIN || (rc >= 0 && (uint)rc < dev->blocksize)) { snd_pcm_wait(dev->handle, 100); } else if(rc == -EPIPE) { snd_pcm_prepare(dev->handle); } } snd_pcm_nonblock(dev->handle, 1); for(int z = 0; z < 16; z++) { rc = snd_pcm_writei(dev->handle, dev->buffer, dev->blocksize); if(rc == -EAGAIN || (rc >= 0 && (uint)rc < dev->blocksize)) { snd_pcm_wait(dev->handle, 100); } else if(rc == -EPIPE) { snd_pcm_prepare(dev->handle); } } snd_pcm_nonblock(dev->handle, 0); } void aud_write(aud_handle *dev, void *data, uint samples) { int rc; uint blocksize = dev->blocksize; while(samples > 0) { blocksize = samples < blocksize ? samples : blocksize; if(dev->handle) { rc = snd_pcm_writei(dev->handle, data, blocksize); } data += blocksize * dev->channels; samples -= blocksize; if(dev->handle) { if(rc == -EAGAIN || (rc >= 0 && (uint)rc < blocksize)) { snd_pcm_wait(dev->handle, 100); } else if(rc == -EPIPE) { #ifdef SND_MSGUNDER if(dev->underruns == 0) { dev->lastunder = dev->pushed; } dev->underruns += 1; #endif snd_pcm_prepare(dev->handle); } else if(rc < 0) { snd_loge(STR_AUD_WRITERR, snd_strerror(rc), rc); } #ifdef SND_MSGUNDER else { dev->pushed += 1; } if((dev->underruns > 0) && ((dev->pushed - dev->lastunder) >= (dev->samplerate / dev->blocksize))) { snd_logw(STR_AUD_UNDERR, dev->underruns); dev->underruns = 0; } #endif } } } uint aud_open_dev(aud_handle *dev, const char *devname, uint samplerate, byte channels, byte width, uint frames, uint bufsize) { dev->channels = (uint)channels; dev->samplerate = samplerate; dev->width = (uint)width; dev->smppos = 0; #ifdef SND_MSGUNDER dev->underruns = 0; dev->lastunder = 0; dev->pushed = 0; #endif dev->handle = NULL; dev->buffer = NULL; dev->conv = NULL; int rc; int subdir = 0; snd_pcm_hw_params_t *params = NULL; rc = snd_pcm_open(&dev->handle, devname, SND_PCM_STREAM_PLAYBACK, 0); if(rc < 0) { snd_loge(STR_AUD_OPNERR, devname, snd_strerror(rc), rc); return 0; } snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(dev->handle, params); snd_pcm_hw_params_set_access(dev->handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); if(snd_pcm_hw_params_set_format(dev->handle, params, (dev->width == 4) ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_S16_LE) != 0) { dev->width = (width == 4) ? 2 : 4; if(snd_pcm_hw_params_set_format(dev->handle, params, (dev->width == 4) ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_S16_LE) != 0) { snd_loge(STR_AUD_FMTERR, devname); snd_pcm_close(dev->handle); return 0; } else { snd_logw(STR_AUD_FMTCH, devname, width * 8, dev->width * 8); } } snd_pcm_hw_params_set_channels(dev->handle, params, dev->channels); snd_pcm_hw_params_set_rate_near(dev->handle, params, &dev->samplerate, &subdir); snd_pcm_uframes_t framesize = frames; snd_pcm_hw_params_set_period_size_near(dev->handle, params, &framesize, &subdir); snd_pcm_uframes_t buffersize = bufsize; if(bufsize > 0) { snd_pcm_hw_params_set_buffer_size_near(dev->handle, params, &buffersize); } rc = snd_pcm_hw_params(dev->handle, params); if(rc < 0) { snd_loge(STR_AUD_HWERR, devname, snd_strerror(rc), rc); snd_pcm_close(dev->handle); return 0; } snd_pcm_hw_params_get_period_size(params, &framesize, &subdir); snd_pcm_hw_params_get_buffer_size(params, &buffersize); snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_current(dev->handle, swparams); // snd_pcm_sw_params_set_avail_min(dev->handle, swparams, framesize); // snd_pcm_sw_params_set_start_threshold(dev->handle, swparams, 0); // snd_pcm_sw_params_set_stop_threshold(dev->handle, swparams, buffersize); rc = snd_pcm_sw_params(dev->handle, swparams); if(rc < 0) { snd_loge(STR_AUD_SWERR, devname, snd_strerror(rc), rc); snd_pcm_close(dev->handle); return 0; } dev->blocksize = framesize; dev->devbufsize = buffersize; dev->buffer = malloc(dev->blocksize * dev->width * dev->channels); if(dev->width == 4) dev->conv = malloc(sizeof(int) * dev->channels); snd_pcm_wait(dev->handle, -1); aud_prepare(dev); return dev->samplerate; } void aud_push_raw(aud_handle *dev, void *data) { memcpy(&((byte*)(dev->buffer))[dev->smppos*dev->channels*dev->width], data, dev->width * dev->channels); dev->smppos++; if(dev->smppos == dev->blocksize) { aud_write(dev, dev->buffer, dev->blocksize); dev->smppos = 0; } } void aud_push(aud_handle *dev, short *data) { if(dev->width == 4) { for(int h = 0; h < dev->channels; h++) { dev->conv[h] = ((int)data[h]) << 16; } aud_push_raw(dev, dev->conv); } else { aud_push_raw(dev, data); } } void aud_flush(aud_handle *dev) { if(dev->smppos != 0) { aud_write(dev, dev->buffer, dev->smppos); dev->smppos = 0; } } void aud_close(aud_handle *dev) { if(dev->handle) { snd_pcm_drain(dev->handle); snd_pcm_close(dev->handle); } free(dev->buffer); if(dev->conv) free(dev->conv); }