First commit
[echo-analysis.git] / src / echorec.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <getopt.h>
4 #include <math.h>
5
6 #include <pulse/pulseaudio.h>
7 #include <pulse/mainloop.h>
8
9 #include "config.h"
10
11
12 enum commands {
13 CMD_CHANNELS = 1,
14 CMD_FORMAT,
15 };
16
17 typedef struct properties {
18 void* play_buffer;
19 size_t play_buffer_pos;
20 size_t play_buffer_length;
21 FILE* rec_file;
22 size_t count;
23 pa_sample_spec sample_spec;
24 pa_mainloop_api* api;
25 } properties;
26
27
28 static void quit_mainloop(properties* prop, int ret) {
29 assert(prop);
30 prop->api->quit(prop->api, ret);
31 }
32
33 static void context_drain_complete(pa_context* c, void* userdata) {
34 (void) userdata;
35 assert(c);
36
37 pa_context_disconnect(c);
38 }
39
40 static void stream_drain_complete(pa_stream* s, int success, void* userdata) {
41 (void) userdata;
42 assert(s);
43
44 if (!success) {
45 fprintf(stderr, "pa_stream_drain() not successful.\n");
46 quit_mainloop((properties*) userdata, EXIT_FAILURE);
47 }
48
49 pa_stream_disconnect(s);
50 pa_stream_unref(s);
51
52 pa_context* context = pa_stream_get_context(s);
53 pa_operation *o;
54 if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
55 pa_context_disconnect(context);
56 else {
57 pa_operation_unref(o);
58 }
59 }
60
61 static void drain_rec_stream(pa_stream* s, properties* prop) {
62 assert(s);
63
64 pa_stream_set_write_callback(s, NULL, NULL);
65
66 pa_operation* o;
67 if (!(o = pa_stream_drain(s, stream_drain_complete, NULL))) {
68 fprintf(stderr, "pa_stream_drain() failed.\n");
69 quit_mainloop(prop, EXIT_FAILURE);
70 }
71
72 pa_operation_unref(o);
73 }
74
75 static size_t stream_write_partial(pa_stream* s, size_t length, properties* prop) {
76 assert(s);
77 assert(length > 0);
78 assert(prop->count > 0);
79
80 // Shorten length to not exceed buffer length
81 const size_t rem = prop->play_buffer_length - prop->play_buffer_pos;
82 if (length > rem)
83 length = rem;
84
85 fprintf(stderr, "Playing %lu bytes...\n", length);
86
87 uint8_t* data = (uint8_t*) prop->play_buffer + prop->play_buffer_pos;
88 if (pa_stream_write(s, data, length, NULL, 0, PA_SEEK_RELATIVE) < 0) {
89 fprintf(stderr, "pa_stream_write() failed.\n");
90 quit_mainloop(prop, EXIT_FAILURE);
91 }
92
93 prop->play_buffer_pos += length;
94 assert(prop->play_buffer_pos <= prop->play_buffer_length);
95
96 // Reached end of buffer
97 if (prop->play_buffer_pos == prop->play_buffer_length) {
98 prop->play_buffer_pos = 0;
99 --prop->count;
100 }
101
102 return length;
103 }
104
105 static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
106 assert(s);
107 assert(length > 0);
108
109 properties* prop = (properties*) userdata;
110 assert(prop);
111
112 // We still have length many bytes to play
113 while (length > 0) {
114 // Play up to length many bytes
115 const size_t ret = stream_write_partial(s, length, prop);
116 length -= ret;
117
118 // No playback anymore
119 if (prop->count == 0) {
120 drain_rec_stream(s, prop);
121 break;
122 }
123 }
124 }
125
126 static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {
127 assert(s);
128 assert(length > 0);
129
130 properties* prop = (properties*) userdata;
131 assert(prop);
132 assert(prop->rec_file);
133
134 const void* data;
135 if (pa_stream_peek(s, &data, &length) < 0) {
136 fprintf(stderr, "pa_stream_peek() failed.\n");
137 quit_mainloop(prop, EXIT_FAILURE);
138 }
139
140 fprintf(stderr, "Recorded %lu bytes...\n", length);
141
142 assert(data);
143 fwrite(data, length, 1, prop->rec_file);
144 fflush(prop->rec_file);
145
146 pa_stream_drop(s);
147 }
148
149 static void context_state_callback(pa_context *c, void *userdata) {
150 pa_stream* play_stream;
151 pa_stream* rec_stream;
152 properties* prop = (properties*) userdata;
153 assert(prop);
154
155 switch (pa_context_get_state(c)) {
156
157 case PA_CONTEXT_READY:
158 if (!(play_stream = pa_stream_new(c, PACKAGE_NAME, &prop->sample_spec, NULL))) {
159 fprintf(stderr, "pa_stream_new() failed.\n");
160 quit_mainloop(prop, EXIT_FAILURE);
161 }
162
163 pa_stream_set_write_callback(play_stream, stream_write_callback, userdata);
164
165 if (pa_stream_connect_playback(play_stream, NULL, NULL, 0, NULL, NULL) < 0) {
166 fprintf(stderr, "pa_stream_connect_playback() failed.\n");
167 quit_mainloop(prop, EXIT_FAILURE);
168 }
169
170 if (!(rec_stream = pa_stream_new(c, PACKAGE_NAME, &prop->sample_spec, NULL))) {
171 fprintf(stderr, "pa_stream_new() failed.\n");
172 quit_mainloop(prop, EXIT_FAILURE);
173 }
174
175 pa_stream_set_read_callback(rec_stream, stream_read_callback, userdata);
176
177 if (pa_stream_connect_record(rec_stream, NULL, NULL, 0) < 0) {
178 fprintf(stderr, "pa_stream_connect_record() failed.\n");
179 quit_mainloop(prop, EXIT_FAILURE);
180 }
181 break;
182
183 case PA_CONTEXT_TERMINATED:
184 quit_mainloop(prop, EXIT_SUCCESS);
185 break;
186
187 case PA_CONTEXT_FAILED:
188 fprintf(stderr, "Connection failed.\n");
189 quit_mainloop(prop, EXIT_FAILURE);
190
191 default:
192 break;
193 }
194 }
195
196 static void cmd_help(const char* argv0) {
197 printf("%s [options]\n"
198 "\n"
199 "Plays repeatedly a given sound file while continuously\n"
200 "recording to another file the acoustic feedback.\n"
201 "\n"
202 "Usage:\n"
203 " %s --help\n"
204 " %s --play=FILENAME\n"
205 "\n"
206 "Options:\n"
207 "\n"
208 " --channels=NUMBER\n"
209 " The number of channels for the play and rec files.\n"
210 " Defaults to 2.\n"
211 "\n"
212 " -c, --count=NUMBER\n"
213 " Stop after playing for the given number of iterations.\n"
214 " Defaults to 0 which means infinity.\n"
215 "\n"
216 " --format=SAMPLEFORMAT\n"
217 " The sample format of the play and rec files. See\n"
218 " pacat --list-file-formats. Defaults to s16le.\n"
219 "\n"
220 " -h, --help\n"
221 " Prints this usage text and exits.\n"
222 "\n"
223 " -p, --play=FILENAME\n"
224 " Plays the raw samples in the given file. If not specified\n"
225 " then stdin is used.\n"
226 "\n"
227 " -r, --rec=FILENAME\n"
228 " The file where the recording is written to. If not\n"
229 " specified then stdout is used.\n"
230 "\n"
231 " -s, --rate=SAMPLERATE\n"
232 " The sample rate of the play and rec files. Defaults\n"
233 " to 44100.\n"
234 "\n"
235 " -V, --version\n"
236 " Prints version information and exits.\n", argv0, argv0, argv0);
237 }
238
239 static void cmd_version() {
240 printf(PACKAGE_STRING "\n");
241 }
242
243 static void read_file(FILE* f, void** data, size_t* length) {
244 assert(f);
245
246 *length = 0L;
247 *data = NULL;
248
249 size_t cap = 0;
250 while (*length == cap) {
251 // Double capacity of buffer, start with 4096 bytes
252 cap = (cap == 0) ? 4096 : 2 * cap;
253 *data = realloc(*data, cap);
254
255 // Could not allocate
256 if (!*data)
257 return;
258
259 // Read into remainder of the buffer
260 uint8_t* rem_data = (uint8_t*) *data + *length;
261 const size_t rem_cap = cap - *length;
262 const size_t ret = fread(rem_data, 1, rem_cap, f);
263 *length += ret;
264
265 // Note that ret < rem_cap iff *length < cap, which
266 // means that there is no more data and we are done.
267 }
268 }
269
270 int main(int argc, char* const argv[]) {
271 int ret = EXIT_FAILURE;
272 pa_mainloop* m = NULL;
273 pa_context* context = NULL;
274 const char* play_fn = NULL;
275 const char* rec_fn = NULL;
276
277 properties prop = {
278 .play_buffer = NULL,
279 .play_buffer_pos = 0L,
280 .play_buffer_length = 0L,
281 .rec_file = stdout,
282 .count = -1,
283 .sample_spec = {
284 .format = PA_SAMPLE_S16LE,
285 .rate = 44100,
286 .channels = 2,
287 },
288 .api = NULL
289 };
290
291 static const struct option long_options[] = {
292 {"channels", 0, NULL, CMD_CHANNELS},
293 {"count", 1, NULL, 'c'},
294 {"format", 1, NULL, CMD_FORMAT},
295 {"help", 0, NULL, 'h'},
296 {"play", 1, NULL, 'p'},
297 {"rec", 1, NULL, 'r'},
298 {"rate", 1, NULL, 's'},
299 {"version", 0, NULL, 'V'},
300 {NULL, 0, NULL, 0}
301 };
302
303 int c;
304 while ((c = getopt_long(argc, argv, "c:f:hp:r:s:V", long_options, NULL)) != -1) {
305 switch (c) {
306 case CMD_CHANNELS:
307 prop.sample_spec.channels = atoi(optarg);
308 break;
309
310 case 'c':
311 prop.count = atoi(optarg);
312 break;
313
314 case CMD_FORMAT:
315 prop.sample_spec.format = pa_parse_sample_format(optarg);
316 break;
317
318 case 'h':
319 cmd_help(argv[0]);
320 return EXIT_SUCCESS;
321
322 case 'p':
323 play_fn = optarg;
324 break;
325
326 case 'r':
327 rec_fn = optarg;
328 break;
329
330 case 's':
331 prop.sample_spec.rate = atoi(optarg);
332 break;
333
334 case 'V':
335 cmd_version();
336 return EXIT_SUCCESS;
337
338 default:
339 goto quit;
340 }
341 }
342
343 FILE* play_file = stdin;
344 if (play_fn)
345 play_file = fopen(play_fn, "r");
346
347 if (!play_file) {
348 perror("Error opening play file.");
349 goto quit;
350 }
351
352 read_file(play_file, &prop.play_buffer, &prop.play_buffer_length);
353
354 if (play_fn)
355 fclose(play_file);
356
357 const size_t samples = prop.play_buffer_length / pa_sample_size(&prop.sample_spec);
358 fprintf(stderr, "Play buffer populated with %lu samples.\n", samples);
359
360 if (samples == 0) {
361 fprintf(stderr, "Could not gather samples from play file.\n");
362 goto quit;
363 }
364
365 if (rec_fn) {
366 prop.rec_file = fopen(rec_fn, "w");
367 if (!prop.rec_file) {
368 perror("Error opening rec file.");
369 goto quit;
370 }
371 }
372
373 if (!(m = pa_mainloop_new())) {
374 fprintf(stderr, "pa_mainloop_new() failed.\n");
375 goto quit;
376 }
377
378 prop.api = pa_mainloop_get_api(m);
379 assert(prop.api);
380
381 if (!(context = pa_context_new(prop.api, PACKAGE_NAME))) {
382 fprintf(stderr, "pa_context_new() failed.\n");
383 goto quit;
384 }
385
386 pa_context_set_state_callback(context, context_state_callback, &prop);
387
388 if (pa_context_connect(context, NULL, 0, NULL) < 0) {
389 fprintf(stderr, "pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(context)));
390 goto quit;
391 }
392
393 if (pa_mainloop_run(m, &ret) < 0) {
394 fprintf(stderr, "pa_mainloop_run() failed.\n");
395 goto quit;
396 }
397
398 fprintf(stderr, "Done.\n");
399
400 quit:
401 if (context)
402 pa_context_unref(context);
403
404 if (m) {
405 pa_signal_done();
406 pa_mainloop_free(m);
407 }
408
409 if (rec_fn)
410 fclose(prop.rec_file);
411
412 if (prop.play_buffer)
413 free(prop.play_buffer);
414
415 return ret;
416 }