#define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include "tune.h" struct tune_dl { void *handle; ino_t inode; struct tune *tune; }; static unsigned int rate = 44100; static unsigned int channels = 1; static unsigned int latency = 500000; /* ring buffer length in us */ static snd_pcm_format_t format = SND_PCM_FORMAT_FLOAT; static const char *dl_fname = "./tune.so"; static int tune_dl_load(struct tune_dl *dl, const char *libfname) { struct stat st; if (stat(libfname, &st) < 0) { warn("stat"); return -1; } if (dl->inode == st.st_ino) { // file unchanged return 0; } // reload the library if (dl->handle) { dlclose(dl->handle); dl->inode = 0; } dl->handle = dlopen(libfname, RTLD_NOW); if (dl->handle == NULL) { // warnx("dlopen: %s", dlerror()); return 0; } dl->tune = dlsym(dl->handle, "TUNE"); if (dl->tune == NULL) { warn("dlsym"); dlclose(dl->handle); return 0; } dl->inode = st.st_ino; if (dl->tune->reload) { dl->tune->reload(dl->tune); } return 0; } /* * Underrun and suspend recovery, from alsa-lib/test/pcm.c */ static int xrun_recovery(snd_pcm_t *handle, int err) { if (err == -EPIPE) { /* under-run */ err = snd_pcm_prepare(handle); if (err < 0) warnx("Can't recovery from underrun, prepare failed: %s", snd_strerror(err)); return 0; } else if (err == -ESTRPIPE) { while ((err = snd_pcm_resume(handle)) == -EAGAIN) sleep(1); /* wait until the suspend flag is released */ if (err < 0) { err = snd_pcm_prepare(handle); if (err < 0) warnx("Can't recovery from suspend, prepare failed: %s", snd_strerror(err)); } return 0; } return err; } static void generate(struct tune_dl *dl, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, int count, double *_phase) { double phase = *_phase; double step = 2. * M_PI/(double)rate; unsigned char *samples[channels]; int steps[channels]; unsigned int chn; int format_bits = snd_pcm_format_width(format); unsigned int maxval = (1 << (format_bits - 1)) - 1; int bps = format_bits / 8; /* bytes per sample */ int phys_bps = snd_pcm_format_physical_width(format) / 8; int big_endian = snd_pcm_format_big_endian(format) == 1; int to_unsigned = snd_pcm_format_unsigned(format) == 1; /* verify and prepare the contents of areas */ for (chn = 0; chn < channels; chn++) { if ((areas[chn].first % 8) != 0) { errx(1, "areas[%i].first == %i, aborting...", chn, areas[chn].first); } samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8)); if ((areas[chn].step % 16) != 0) { errx(1, "areas[%i].step == %i, aborting...", chn, areas[chn].step); } steps[chn] = areas[chn].step / 8; samples[chn] += offset * steps[chn]; } tune_dl_load(dl, dl_fname); /* fill the channel areas */ while (count-- > 0) { union { float f; int i; } fval; int res, i; if (dl->handle && dl->tune->play) { fval.f = dl->tune->play(dl->tune, phase); } else { fval.f = 0; } res = fval.i; if (to_unsigned) res ^= 1U << (format_bits - 1); for (chn = 0; chn < channels; chn++) { /* Generate data in native endian format */ if (big_endian) { for (i = 0; i < bps; i++) *(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff; } else { for (i = 0; i < bps; i++) *(samples[chn] + i) = (res >> i * 8) & 0xff; } samples[chn] += steps[chn]; } phase += step; /* if (phase >= max_phase) phase -= max_phase; */ } *_phase = phase; } int main(int argc, char *argv[]) { struct tune_dl dl = {0}; int err; snd_pcm_t *pcm_out; const char *device_out = "default"; if (argc > 2) { device_out = argv[2]; } if (tune_dl_load(&dl, dl_fname) < 0) { return 1; } if ((err = snd_pcm_open(&pcm_out, device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { warnx("Playback open error: %s", snd_strerror(err)); return 1; } if ((err = snd_pcm_set_params(pcm_out, format, SND_PCM_ACCESS_MMAP_INTERLEAVED, channels, rate, 1, latency)) < 0) { warnx("Playback open error: %s", snd_strerror(err)); return 1; } if (dl.tune->init) { dl.tune->init(dl.tune); } snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t period_size; if ((err = snd_pcm_get_params(pcm_out, &buffer_size, &period_size)) < 0) { warnx("Playback get params error: %s", snd_strerror(err)); return 1; } double phase = 0; const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t offset, frames, size; snd_pcm_sframes_t avail, commitres; snd_pcm_state_t state; int first = 1; while (1) { state = snd_pcm_state(pcm_out); if (state == SND_PCM_STATE_XRUN) { err = xrun_recovery(pcm_out, -EPIPE); if (err < 0) { printf("XRUN recovery failed: %s\n", snd_strerror(err)); return err; } first = 1; } else if (state == SND_PCM_STATE_SUSPENDED) { err = xrun_recovery(pcm_out, -ESTRPIPE); if (err < 0) { printf("SUSPEND recovery failed: %s\n", snd_strerror(err)); return err; } } avail = snd_pcm_avail_update(pcm_out); if (avail < 0) { err = xrun_recovery(pcm_out, avail); if (err < 0) { printf("avail update failed: %s\n", snd_strerror(err)); return err; } first = 1; continue; } if (avail < period_size) { if (first) { first = 0; err = snd_pcm_start(pcm_out); if (err < 0) { printf("Start error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } } else { err = snd_pcm_wait(pcm_out, -1); if (err < 0) { if ((err = xrun_recovery(pcm_out, err)) < 0) { printf("snd_pcm_wait error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } first = 1; } } continue; } size = period_size; while (size > 0) { frames = size; err = snd_pcm_mmap_begin(pcm_out, &my_areas, &offset, &frames); if (err < 0) { if ((err = xrun_recovery(pcm_out, err)) < 0) { printf("MMAP begin avail error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } first = 1; } generate(&dl, my_areas, offset, frames, &phase); // generate_sine(my_areas, offset, frames, &phase); commitres = snd_pcm_mmap_commit(pcm_out, offset, frames); if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) { if ((err = xrun_recovery(pcm_out, commitres >= 0 ? -EPIPE : commitres)) < 0) { printf("MMAP commit error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } first = 1; } size -= frames; } } if (dl.tune->deinit) { dl.tune->deinit(dl.tune); } snd_pcm_close(pcm_out); }