studio.cView |
---|
1 | 1 … | #define _DEFAULT_SOURCE |
| 2 … | +#include <alsa/asoundlib.h> |
2 | 3 … | #include <dlfcn.h> |
3 | 4 … | #include <err.h> |
| 5 … | +#include <math.h> |
4 | 6 … | #include <stdbool.h> |
5 | 7 … | #include <stdio.h> |
6 | 8 … | #include <sys/stat.h> |
7 | 9 … | #include <sys/types.h> |
13 | 15 … | ino_t inode; |
14 | 16 … | struct tune *tune; |
15 | 17 … | }; |
16 | 18 … | |
| 19 … | +static unsigned int rate = 44100; |
| 20 … | +static unsigned int channels = 1; |
| 21 … | +static unsigned int latency = 500000; |
| 22 … | +static snd_pcm_format_t format = SND_PCM_FORMAT_FLOAT; |
| 23 … | +static const char *dl_fname = "./tune.so"; |
| 24 … | + |
17 | 25 … | static int tune_dl_load(struct tune_dl *dl, const char *libfname) |
18 | 26 … | { |
19 | 27 … | struct stat st; |
20 | 28 … | if (stat(libfname, &st) < 0) { |
47 | 55 … | } |
48 | 56 … | return 0; |
49 | 57 … | } |
50 | 58 … | |
| 59 … | + |
| 60 … | + * Underrun and suspend recovery, from alsa-lib/test/pcm.c |
| 61 … | + */ |
| 62 … | + |
| 63 … | +static int xrun_recovery(snd_pcm_t *handle, int err) |
| 64 … | +{ |
| 65 … | + if (err == -EPIPE) { |
| 66 … | + err = snd_pcm_prepare(handle); |
| 67 … | + if (err < 0) |
| 68 … | + warnx("Can't recovery from underrun, prepare failed: %s", snd_strerror(err)); |
| 69 … | + return 0; |
| 70 … | + } else if (err == -ESTRPIPE) { |
| 71 … | + while ((err = snd_pcm_resume(handle)) == -EAGAIN) |
| 72 … | + sleep(1); |
| 73 … | + if (err < 0) { |
| 74 … | + err = snd_pcm_prepare(handle); |
| 75 … | + if (err < 0) |
| 76 … | + warnx("Can't recovery from suspend, prepare failed: %s", snd_strerror(err)); |
| 77 … | + } |
| 78 … | + return 0; |
| 79 … | + } |
| 80 … | + return err; |
| 81 … | +} |
| 82 … | + |
| 83 … | +static void generate(struct tune_dl *dl, |
| 84 … | + const snd_pcm_channel_area_t *areas, |
| 85 … | + snd_pcm_uframes_t offset, |
| 86 … | + int count, double *_phase) |
| 87 … | +{ |
| 88 … | + double phase = *_phase; |
| 89 … | + double step = 2. * M_PI/(double)rate; |
| 90 … | + unsigned char *samples[channels]; |
| 91 … | + int steps[channels]; |
| 92 … | + unsigned int chn; |
| 93 … | + int format_bits = snd_pcm_format_width(format); |
| 94 … | + unsigned int maxval = (1 << (format_bits - 1)) - 1; |
| 95 … | + int bps = format_bits / 8; |
| 96 … | + int phys_bps = snd_pcm_format_physical_width(format) / 8; |
| 97 … | + int big_endian = snd_pcm_format_big_endian(format) == 1; |
| 98 … | + int to_unsigned = snd_pcm_format_unsigned(format) == 1; |
| 99 … | + int is_float = ( |
| 100 … | + format == SND_PCM_FORMAT_FLOAT_LE || |
| 101 … | + format == SND_PCM_FORMAT_FLOAT_BE || |
| 102 … | + format == SND_PCM_FORMAT_FLOAT64_LE || |
| 103 … | + format == SND_PCM_FORMAT_FLOAT64_BE); |
| 104 … | + |
| 105 … | + |
| 106 … | + for (chn = 0; chn < channels; chn++) { |
| 107 … | + if ((areas[chn].first % 8) != 0) { |
| 108 … | + errx(1, "areas[%i].first == %i, aborting...", chn, areas[chn].first); |
| 109 … | + } |
| 110 … | + samples[chn] = (((unsigned char *)areas[chn].addr) + (areas[chn].first / 8)); |
| 111 … | + if ((areas[chn].step % 16) != 0) { |
| 112 … | + errx(1, "areas[%i].step == %i, aborting...", chn, areas[chn].step); |
| 113 … | + } |
| 114 … | + steps[chn] = areas[chn].step / 8; |
| 115 … | + samples[chn] += offset * steps[chn]; |
| 116 … | + } |
| 117 … | + |
| 118 … | + tune_dl_load(dl, dl_fname); |
| 119 … | + |
| 120 … | + |
| 121 … | + while (count-- > 0) { |
| 122 … | + union { |
| 123 … | + float f; |
| 124 … | + int i; |
| 125 … | + } fval; |
| 126 … | + int res, i; |
| 127 … | + if (is_float) { |
| 128 … | + if (dl->handle && dl->tune->play) { |
| 129 … | + fval.f = dl->tune->play(dl->tune, phase); |
| 130 … | + } else { |
| 131 … | + |
| 132 … | + fval.f = 0; |
| 133 … | + } |
| 134 … | + res = fval.i; |
| 135 … | + } else |
| 136 … | + res = sin(phase) * maxval; |
| 137 … | + if (to_unsigned) |
| 138 … | + res ^= 1U << (format_bits - 1); |
| 139 … | + for (chn = 0; chn < channels; chn++) { |
| 140 … | + |
| 141 … | + if (big_endian) { |
| 142 … | + for (i = 0; i < bps; i++) |
| 143 … | + *(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff; |
| 144 … | + } else { |
| 145 … | + for (i = 0; i < bps; i++) |
| 146 … | + *(samples[chn] + i) = (res >> i * 8) & 0xff; |
| 147 … | + } |
| 148 … | + samples[chn] += steps[chn]; |
| 149 … | + } |
| 150 … | + phase += step; |
| 151 … | + |
| 152 … | + if (phase >= max_phase) |
| 153 … | + phase -= max_phase; |
| 154 … | + */ |
| 155 … | + } |
| 156 … | + *_phase = phase; |
| 157 … | +} |
| 158 … | + |
| 159 … | + |
51 | 160 … | int main(int argc, char *argv[]) |
52 | 161 … | { |
53 | 162 … | struct tune_dl dl = {0}; |
54 | | - const char *fname = "./tune.so"; |
55 | | - if (tune_dl_load(&dl, fname) < 0) { |
| 163 … | + int err; |
| 164 … | + snd_pcm_t *pcm_out; |
| 165 … | + const char *device_out = "default"; |
| 166 … | + |
| 167 … | + if (argc > 2) { |
| 168 … | + device_out = argv[2]; |
| 169 … | + } |
| 170 … | + |
| 171 … | + if (tune_dl_load(&dl, dl_fname) < 0) { |
56 | 172 … | return 1; |
57 | 173 … | } |
| 174 … | + |
| 175 … | + if ((err = snd_pcm_open(&pcm_out, device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { |
| 176 … | + warnx("Playback open error: %s", snd_strerror(err)); |
| 177 … | + return 1; |
| 178 … | + } |
| 179 … | + |
| 180 … | + if ((err = snd_pcm_set_params(pcm_out, |
| 181 … | + format, |
| 182 … | + SND_PCM_ACCESS_MMAP_INTERLEAVED, |
| 183 … | + channels, |
| 184 … | + rate, |
| 185 … | + 1, |
| 186 … | + latency)) < 0) { |
| 187 … | + warnx("Playback open error: %s", snd_strerror(err)); |
| 188 … | + return 1; |
| 189 … | + } |
| 190 … | + |
58 | 191 … | if (dl.tune->init) { |
59 | 192 … | dl.tune->init(dl.tune); |
60 | 193 … | } |
61 | | - int t = 0; |
| 194 … | + |
| 195 … | + snd_pcm_uframes_t buffer_size; |
| 196 … | + snd_pcm_uframes_t period_size; |
| 197 … | + if ((err = snd_pcm_get_params(pcm_out, &buffer_size, &period_size)) < 0) { |
| 198 … | + warnx("Playback get params error: %s", snd_strerror(err)); |
| 199 … | + return 1; |
| 200 … | + } |
| 201 … | + |
| 202 … | + double phase = 0; |
| 203 … | + const snd_pcm_channel_area_t *my_areas; |
| 204 … | + snd_pcm_uframes_t offset, frames, size; |
| 205 … | + snd_pcm_sframes_t avail, commitres; |
| 206 … | + snd_pcm_state_t state; |
| 207 … | + int first = 1; |
| 208 … | + |
62 | 209 … | while (1) { |
63 | | - (void) tune_dl_load(&dl, fname); |
64 | | - if (dl.handle && dl.tune->play) { |
65 | | - double sample = dl.tune->play(dl.tune, t); |
66 | | - printf("%d %f\n", t, sample); |
| 210 … | + state = snd_pcm_state(pcm_out); |
| 211 … | + if (state == SND_PCM_STATE_XRUN) { |
| 212 … | + err = xrun_recovery(pcm_out, -EPIPE); |
| 213 … | + if (err < 0) { |
| 214 … | + printf("XRUN recovery failed: %s\n", snd_strerror(err)); |
| 215 … | + return err; |
| 216 … | + } |
| 217 … | + first = 1; |
| 218 … | + } else if (state == SND_PCM_STATE_SUSPENDED) { |
| 219 … | + err = xrun_recovery(pcm_out, -ESTRPIPE); |
| 220 … | + if (err < 0) { |
| 221 … | + printf("SUSPEND recovery failed: %s\n", snd_strerror(err)); |
| 222 … | + return err; |
| 223 … | + } |
67 | 224 … | } |
68 | | - (void) tune_dl_load(&dl, fname); |
69 | | - usleep(100000); |
70 | | - t++; |
| 225 … | + avail = snd_pcm_avail_update(pcm_out); |
| 226 … | + if (avail < 0) { |
| 227 … | + err = xrun_recovery(pcm_out, avail); |
| 228 … | + if (err < 0) { |
| 229 … | + printf("avail update failed: %s\n", snd_strerror(err)); |
| 230 … | + return err; |
| 231 … | + } |
| 232 … | + first = 1; |
| 233 … | + continue; |
| 234 … | + } |
| 235 … | + if (avail < period_size) { |
| 236 … | + if (first) { |
| 237 … | + first = 0; |
| 238 … | + err = snd_pcm_start(pcm_out); |
| 239 … | + if (err < 0) { |
| 240 … | + printf("Start error: %s\n", snd_strerror(err)); |
| 241 … | + exit(EXIT_FAILURE); |
| 242 … | + } |
| 243 … | + } else { |
| 244 … | + err = snd_pcm_wait(pcm_out, -1); |
| 245 … | + if (err < 0) { |
| 246 … | + if ((err = xrun_recovery(pcm_out, err)) < 0) { |
| 247 … | + printf("snd_pcm_wait error: %s\n", snd_strerror(err)); |
| 248 … | + exit(EXIT_FAILURE); |
| 249 … | + } |
| 250 … | + first = 1; |
| 251 … | + } |
| 252 … | + } |
| 253 … | + continue; |
| 254 … | + } |
| 255 … | + size = period_size; |
| 256 … | + while (size > 0) { |
| 257 … | + frames = size; |
| 258 … | + err = snd_pcm_mmap_begin(pcm_out, &my_areas, &offset, &frames); |
| 259 … | + if (err < 0) { |
| 260 … | + if ((err = xrun_recovery(pcm_out, err)) < 0) { |
| 261 … | + printf("MMAP begin avail error: %s\n", snd_strerror(err)); |
| 262 … | + exit(EXIT_FAILURE); |
| 263 … | + } |
| 264 … | + first = 1; |
| 265 … | + } |
| 266 … | + generate(&dl, my_areas, offset, frames, &phase); |
| 267 … | + |
| 268 … | + commitres = snd_pcm_mmap_commit(pcm_out, offset, frames); |
| 269 … | + if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) { |
| 270 … | + if ((err = xrun_recovery(pcm_out, commitres >= 0 ? -EPIPE : commitres)) < 0) { |
| 271 … | + printf("MMAP commit error: %s\n", snd_strerror(err)); |
| 272 … | + exit(EXIT_FAILURE); |
| 273 … | + } |
| 274 … | + first = 1; |
| 275 … | + } |
| 276 … | + size -= frames; |
| 277 … | + } |
| 278 … | + |
71 | 279 … | } |
72 | 280 … | if (dl.tune->deinit) { |
73 | 281 … | dl.tune->deinit(dl.tune); |
74 | 282 … | } |
| 283 … | + |
| 284 … | + snd_pcm_close(pcm_out); |
75 | 285 … | } |