6 #include <pulse/pulseaudio.h>
7 #include <pulse/mainloop.h>
17 typedef struct properties
{
19 size_t play_buffer_pos
;
20 size_t play_buffer_length
;
23 pa_sample_spec sample_spec
;
28 static void quit_mainloop(properties
* prop
, int ret
) {
30 prop
->api
->quit(prop
->api
, ret
);
33 static void context_drain_complete(pa_context
* c
, void* userdata
) {
37 pa_context_disconnect(c
);
40 static void stream_drain_complete(pa_stream
* s
, int success
, void* userdata
) {
45 fprintf(stderr
, "pa_stream_drain() not successful.\n");
46 quit_mainloop((properties
*) userdata
, EXIT_FAILURE
);
49 pa_stream_disconnect(s
);
52 pa_context
* context
= pa_stream_get_context(s
);
54 if (!(o
= pa_context_drain(context
, context_drain_complete
, NULL
)))
55 pa_context_disconnect(context
);
57 pa_operation_unref(o
);
61 static void drain_rec_stream(pa_stream
* s
, properties
* prop
) {
64 pa_stream_set_write_callback(s
, NULL
, NULL
);
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
);
72 pa_operation_unref(o
);
75 static size_t stream_write_partial(pa_stream
* s
, size_t length
, properties
* prop
) {
78 assert(prop
->count
> 0);
80 // Shorten length to not exceed buffer length
81 const size_t rem
= prop
->play_buffer_length
- prop
->play_buffer_pos
;
85 fprintf(stderr
, "Playing %lu bytes...\n", length
);
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
);
93 prop
->play_buffer_pos
+= length
;
94 assert(prop
->play_buffer_pos
<= prop
->play_buffer_length
);
96 // Reached end of buffer
97 if (prop
->play_buffer_pos
== prop
->play_buffer_length
) {
98 prop
->play_buffer_pos
= 0;
105 static void stream_write_callback(pa_stream
*s
, size_t length
, void *userdata
) {
109 properties
* prop
= (properties
*) userdata
;
112 // We still have length many bytes to play
114 // Play up to length many bytes
115 const size_t ret
= stream_write_partial(s
, length
, prop
);
118 // No playback anymore
119 if (prop
->count
== 0) {
120 drain_rec_stream(s
, prop
);
126 static void stream_read_callback(pa_stream
*s
, size_t length
, void *userdata
) {
130 properties
* prop
= (properties
*) userdata
;
132 assert(prop
->rec_file
);
135 if (pa_stream_peek(s
, &data
, &length
) < 0) {
136 fprintf(stderr
, "pa_stream_peek() failed.\n");
137 quit_mainloop(prop
, EXIT_FAILURE
);
140 fprintf(stderr
, "Recorded %lu bytes...\n", length
);
143 fwrite(data
, length
, 1, prop
->rec_file
);
144 fflush(prop
->rec_file
);
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
;
155 switch (pa_context_get_state(c
)) {
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
);
163 pa_stream_set_write_callback(play_stream
, stream_write_callback
, userdata
);
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
);
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
);
175 pa_stream_set_read_callback(rec_stream
, stream_read_callback
, userdata
);
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
);
183 case PA_CONTEXT_TERMINATED
:
184 quit_mainloop(prop
, EXIT_SUCCESS
);
187 case PA_CONTEXT_FAILED
:
188 fprintf(stderr
, "Connection failed.\n");
189 quit_mainloop(prop
, EXIT_FAILURE
);
196 static void cmd_help(const char* argv0
) {
197 printf("%s [options]\n"
199 "Plays repeatedly a given sound file while continuously\n"
200 "recording to another file the acoustic feedback.\n"
204 " %s --play=FILENAME\n"
208 " --channels=NUMBER\n"
209 " The number of channels for the play and rec files.\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"
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"
221 " Prints this usage text and exits.\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"
227 " -r, --rec=FILENAME\n"
228 " The file where the recording is written to. If not\n"
229 " specified then stdout is used.\n"
231 " -s, --rate=SAMPLERATE\n"
232 " The sample rate of the play and rec files. Defaults\n"
236 " Prints version information and exits.\n", argv0
, argv0
, argv0
);
239 static void cmd_version() {
240 printf(PACKAGE_STRING
"\n");
243 static void read_file(FILE* f
, void** data
, size_t* length
) {
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
);
255 // Could not allocate
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
);
265 // Note that ret < rem_cap iff *length < cap, which
266 // means that there is no more data and we are done.
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
;
279 .play_buffer_pos
= 0L,
280 .play_buffer_length
= 0L,
284 .format
= PA_SAMPLE_S16LE
,
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'},
304 while ((c
= getopt_long(argc
, argv
, "c:f:hp:r:s:V", long_options
, NULL
)) != -1) {
307 prop
.sample_spec
.channels
= atoi(optarg
);
311 prop
.count
= atoi(optarg
);
315 prop
.sample_spec
.format
= pa_parse_sample_format(optarg
);
331 prop
.sample_spec
.rate
= atoi(optarg
);
343 FILE* play_file
= stdin
;
345 play_file
= fopen(play_fn
, "r");
348 perror("Error opening play file.");
352 read_file(play_file
, &prop
.play_buffer
, &prop
.play_buffer_length
);
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
);
361 fprintf(stderr
, "Could not gather samples from play file.\n");
366 prop
.rec_file
= fopen(rec_fn
, "w");
367 if (!prop
.rec_file
) {
368 perror("Error opening rec file.");
373 if (!(m
= pa_mainloop_new())) {
374 fprintf(stderr
, "pa_mainloop_new() failed.\n");
378 prop
.api
= pa_mainloop_get_api(m
);
381 if (!(context
= pa_context_new(prop
.api
, PACKAGE_NAME
))) {
382 fprintf(stderr
, "pa_context_new() failed.\n");
386 pa_context_set_state_callback(context
, context_state_callback
, &prop
);
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
)));
393 if (pa_mainloop_run(m
, &ret
) < 0) {
394 fprintf(stderr
, "pa_mainloop_run() failed.\n");
398 fprintf(stderr
, "Done.\n");
402 pa_context_unref(context
);
410 fclose(prop
.rec_file
);
412 if (prop
.play_buffer
)
413 free(prop
.play_buffer
);