2 This file is part of PulseAudio.
4 Copyright 2004-2006 Lennart Poettering
5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
38 #include <pulse/i18n.h>
39 #include <pulse/pulseaudio.h>
41 #define TIME_EVENT_USEC 50000
43 #define CLEAR_LINE "\x1B[K"
45 static enum { RECORD, PLAYBACK } mode = PLAYBACK;
47 static pa_context *context = NULL;
48 static pa_stream *stream = NULL;
49 static pa_mainloop_api *mainloop_api = NULL;
51 static void *buffer = NULL;
52 static size_t buffer_length = 0, buffer_index = 0;
54 static pa_io_event* stdio_event = NULL;
56 static char *stream_name = NULL, *client_name = NULL, *device = NULL;
58 static int verbose = 0;
59 static pa_volume_t volume = PA_VOLUME_NORM;
60 static int volume_is_set = 0;
62 static pa_sample_spec sample_spec = {
63 .format = PA_SAMPLE_S16LE,
68 static pa_channel_map channel_map;
69 static int channel_map_set = 0;
71 static pa_stream_flags_t flags = 0;
73 static size_t latency = 0, process_time=0;
75 /* A shortcut for terminating the application */
76 static void quit(int ret) {
78 mainloop_api->quit(mainloop_api, ret);
81 /* Write some data to the stream */
82 static void do_stream_write(size_t length) {
86 if (!buffer || !buffer_length)
90 if (l > buffer_length)
93 if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) {
94 fprintf(stderr, _("pa_stream_write() failed: %s\n"), pa_strerror(pa_context_errno(context)));
102 if (!buffer_length) {
105 buffer_index = buffer_length = 0;
109 /* This is called whenever new data may be written to the stream */
110 static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
115 mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT);
120 do_stream_write(length);
123 /* This is called whenever new data may is available */
124 static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {
130 mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT);
132 if (pa_stream_peek(s, &data, &length) < 0) {
133 fprintf(stderr, _("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context)));
142 buffer = pa_xrealloc(buffer, buffer_length + length);
143 memcpy((uint8_t*) buffer + buffer_length, data, length);
144 buffer_length += length;
146 buffer = pa_xmalloc(length);
147 memcpy(buffer, data, length);
148 buffer_length = length;
155 /* This routine is called whenever the stream state changes */
156 static void stream_state_callback(pa_stream *s, void *userdata) {
159 switch (pa_stream_get_state(s)) {
160 case PA_STREAM_CREATING:
161 case PA_STREAM_TERMINATED:
164 case PA_STREAM_READY:
166 const pa_buffer_attr *a;
167 char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
169 fprintf(stderr, _("Stream successfully created.\n"));
171 if (!(a = pa_stream_get_buffer_attr(s)))
172 fprintf(stderr, _("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));
175 if (mode == PLAYBACK)
176 fprintf(stderr, _("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq);
178 assert(mode == RECORD);
179 fprintf(stderr, _("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize);
183 fprintf(stderr, _("Using sample spec '%s', channel map '%s'.\n"),
184 pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
185 pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s)));
187 fprintf(stderr, _("Connected to device %s (%u, %ssuspended).\n"),
188 pa_stream_get_device_name(s),
189 pa_stream_get_device_index(s),
190 pa_stream_is_suspended(s) ? "" : "not ");
195 case PA_STREAM_FAILED:
197 fprintf(stderr, _("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s))));
202 static void stream_suspended_callback(pa_stream *s, void *userdata) {
206 if (pa_stream_is_suspended(s))
207 fprintf(stderr, _("Stream device suspended.%s \n"), CLEAR_LINE);
209 fprintf(stderr, _("Stream device resumed.%s \n"), CLEAR_LINE);
213 static void stream_underflow_callback(pa_stream *s, void *userdata) {
217 fprintf(stderr, _("Stream underrun.%s \n"), CLEAR_LINE);
220 static void stream_overflow_callback(pa_stream *s, void *userdata) {
224 fprintf(stderr, _("Stream overrun.%s \n"), CLEAR_LINE);
227 static void stream_started_callback(pa_stream *s, void *userdata) {
231 fprintf(stderr, _("Stream started.%s \n"), CLEAR_LINE);
234 static void stream_moved_callback(pa_stream *s, void *userdata) {
238 fprintf(stderr, _("Stream moved to device %s (%u, %ssuspended).%s \n"), pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : _("not "), CLEAR_LINE);
241 static void stream_buffer_attr_callback(pa_stream *s, void *userdata) {
245 fprintf(stderr, _("Stream buffer attributes changed.%s \n"), CLEAR_LINE);
248 static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *pl, void *userdata) {
255 t = pa_proplist_to_string_sep(pl, ", ");
256 fprintf(stderr, "Got event '%s', properties '%s'\n", name, t);
260 /* This is called whenever the context status changes */
261 static void context_state_callback(pa_context *c, void *userdata) {
264 switch (pa_context_get_state(c)) {
265 case PA_CONTEXT_CONNECTING:
266 case PA_CONTEXT_AUTHORIZING:
267 case PA_CONTEXT_SETTING_NAME:
270 case PA_CONTEXT_READY: {
272 pa_buffer_attr buffer_attr;
278 fprintf(stderr, _("Connection established.%s \n"), CLEAR_LINE);
280 if (!(stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL))) {
281 fprintf(stderr, _("pa_stream_new() failed: %s\n"), pa_strerror(pa_context_errno(c)));
285 pa_stream_set_state_callback(stream, stream_state_callback, NULL);
286 pa_stream_set_write_callback(stream, stream_write_callback, NULL);
287 pa_stream_set_read_callback(stream, stream_read_callback, NULL);
288 pa_stream_set_suspended_callback(stream, stream_suspended_callback, NULL);
289 pa_stream_set_moved_callback(stream, stream_moved_callback, NULL);
290 pa_stream_set_underflow_callback(stream, stream_underflow_callback, NULL);
291 pa_stream_set_overflow_callback(stream, stream_overflow_callback, NULL);
292 pa_stream_set_started_callback(stream, stream_started_callback, NULL);
293 pa_stream_set_event_callback(stream, stream_event_callback, NULL);
294 pa_stream_set_buffer_attr_callback(stream, stream_buffer_attr_callback, NULL);
297 memset(&buffer_attr, 0, sizeof(buffer_attr));
298 buffer_attr.tlength = (uint32_t) latency;
299 buffer_attr.minreq = (uint32_t) process_time;
300 buffer_attr.maxlength = (uint32_t) -1;
301 buffer_attr.prebuf = (uint32_t) -1;
302 buffer_attr.fragsize = (uint32_t) latency;
303 flags |= PA_STREAM_ADJUST_LATENCY;
306 if (mode == PLAYBACK) {
308 if ((r = pa_stream_connect_playback(stream, device, latency > 0 ? &buffer_attr : NULL, flags, volume_is_set ? pa_cvolume_set(&cv, sample_spec.channels, volume) : NULL, NULL)) < 0) {
309 fprintf(stderr, _("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c)));
314 if ((r = pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : NULL, flags)) < 0) {
315 fprintf(stderr, _("pa_stream_connect_record() failed: %s\n"), pa_strerror(pa_context_errno(c)));
323 case PA_CONTEXT_TERMINATED:
327 case PA_CONTEXT_FAILED:
329 fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c)));
340 /* Connection draining complete */
341 static void context_drain_complete(pa_context*c, void *userdata) {
342 pa_context_disconnect(c);
345 /* Stream draining complete */
346 static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
349 fprintf(stderr, _("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context)));
354 fprintf(stderr, _("Playback stream drained.\n"));
356 pa_stream_disconnect(stream);
357 pa_stream_unref(stream);
360 if (!pa_context_drain(context, context_drain_complete, NULL))
361 pa_context_disconnect(context);
364 fprintf(stderr, _("Draining connection to server.\n"));
368 /* New data on STDIN **/
369 static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
373 assert(a == mainloop_api);
375 assert(stdio_event == e);
378 mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL);
382 if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY || !(l = w = pa_stream_writable_size(stream)))
385 buffer = pa_xmalloc(l);
387 if ((r = read(fd, buffer, l)) <= 0) {
390 fprintf(stderr, _("Got EOF.\n"));
395 if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) {
396 fprintf(stderr, _("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context)));
401 pa_operation_unref(o);
406 fprintf(stderr, _("read() failed: %s\n"), strerror(errno));
410 mainloop_api->io_free(stdio_event);
415 buffer_length = (uint32_t) r;
422 /* Some data may be written to STDOUT */
423 static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
426 assert(a == mainloop_api);
428 assert(stdio_event == e);
431 mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL);
435 assert(buffer_length);
437 if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) {
438 fprintf(stderr, _("write() failed: %s\n"), strerror(errno));
441 mainloop_api->io_free(stdio_event);
446 buffer_length -= (uint32_t) r;
447 buffer_index += (uint32_t) r;
449 if (!buffer_length) {
452 buffer_length = buffer_index = 0;
456 /* UNIX signal to quit recieved */
457 static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
459 fprintf(stderr, _("Got signal, exiting.\n"));
463 /* Show the current latency */
464 static void stream_update_timing_callback(pa_stream *s, int success, void *userdata) {
471 pa_stream_get_time(s, &usec) < 0 ||
472 pa_stream_get_latency(s, &l, &negative) < 0) {
473 fprintf(stderr, _("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context)));
478 fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec. \r"),
479 (float) usec / 1000000,
480 (float) l * (negative?-1.0f:1.0f));
483 /* Someone requested that the latency is shown */
484 static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
489 pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL));
492 static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) {
495 if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) {
497 if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)))
498 fprintf(stderr, _("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context)));
500 pa_operation_unref(o);
503 pa_gettimeofday(&next);
504 pa_timeval_add(&next, TIME_EVENT_USEC);
506 m->time_restart(e, &next);
509 static void help(const char *argv0) {
511 printf(_("%s [options]\n\n"
512 " -h, --help Show this help\n"
513 " --version Show version\n\n"
514 " -r, --record Create a connection for recording\n"
515 " -p, --playback Create a connection for playback\n\n"
516 " -v, --verbose Enable verbose operations\n\n"
517 " -s, --server=SERVER The name of the server to connect to\n"
518 " -d, --device=DEVICE The name of the sink/source to connect to\n"
519 " -n, --client-name=NAME How to call this client on the server\n"
520 " --stream-name=NAME How to call this stream on the server\n"
521 " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n"
522 " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n"
523 " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n"
524 " float32be, ulaw, alaw, s32le, s32be (defaults to s16ne)\n"
525 " --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n"
527 " --channel-map=CHANNELMAP Channel map to use instead of the default\n"
528 " --fix-format Take the sample format from the sink the stream is\n"
529 " being connected to.\n"
530 " --fix-rate Take the sampling rate from the sink the stream is\n"
531 " being connected to.\n"
532 " --fix-channels Take the number of channels and the channel map\n"
533 " from the sink the stream is being connected to.\n"
534 " --no-remix Don't upmix or downmix channels.\n"
535 " --no-remap Map channels by index instead of name.\n"
536 " --latency=BYTES Request the specified latency in bytes.\n"
537 " --process-time=BYTES Request the specified process time per request in bytes.\n")
559 int main(int argc, char *argv[]) {
560 pa_mainloop* m = NULL;
562 char *bn, *server = NULL;
563 pa_time_event *time_event = NULL;
565 static const struct option long_options[] = {
566 {"record", 0, NULL, 'r'},
567 {"playback", 0, NULL, 'p'},
568 {"device", 1, NULL, 'd'},
569 {"server", 1, NULL, 's'},
570 {"client-name", 1, NULL, 'n'},
571 {"stream-name", 1, NULL, ARG_STREAM_NAME},
572 {"version", 0, NULL, ARG_VERSION},
573 {"help", 0, NULL, 'h'},
574 {"verbose", 0, NULL, 'v'},
575 {"volume", 1, NULL, ARG_VOLUME},
576 {"rate", 1, NULL, ARG_SAMPLERATE},
577 {"format", 1, NULL, ARG_SAMPLEFORMAT},
578 {"channels", 1, NULL, ARG_CHANNELS},
579 {"channel-map", 1, NULL, ARG_CHANNELMAP},
580 {"fix-format", 0, NULL, ARG_FIX_FORMAT},
581 {"fix-rate", 0, NULL, ARG_FIX_RATE},
582 {"fix-channels", 0, NULL, ARG_FIX_CHANNELS},
583 {"no-remap", 0, NULL, ARG_NO_REMAP},
584 {"no-remix", 0, NULL, ARG_NO_REMIX},
585 {"latency", 1, NULL, ARG_LATENCY},
586 {"process-time", 1, NULL, ARG_PROCESS_TIME},
590 setlocale(LC_ALL, "");
591 bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR);
593 if (!(bn = strrchr(argv[0], '/')))
598 if (strstr(bn, "rec") || strstr(bn, "mon"))
600 else if (strstr(bn, "cat") || strstr(bn, "play"))
603 while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) {
612 printf(_("pacat %s\nCompiled with libpulse %s\nLinked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version());
626 device = pa_xstrdup(optarg);
631 server = pa_xstrdup(optarg);
635 pa_xfree(client_name);
636 client_name = pa_xstrdup(optarg);
639 case ARG_STREAM_NAME:
640 pa_xfree(stream_name);
641 stream_name = pa_xstrdup(optarg);
649 int v = atoi(optarg);
650 volume = v < 0 ? 0U : (pa_volume_t) v;
656 sample_spec.channels = (uint8_t) atoi(optarg);
659 case ARG_SAMPLEFORMAT:
660 sample_spec.format = pa_parse_sample_format(optarg);
664 sample_spec.rate = (uint32_t) atoi(optarg);
668 if (!pa_channel_map_parse(&channel_map, optarg)) {
669 fprintf(stderr, _("Invalid channel map '%s'\n"), optarg);
676 case ARG_FIX_CHANNELS:
677 flags |= PA_STREAM_FIX_CHANNELS;
681 flags |= PA_STREAM_FIX_RATE;
685 flags |= PA_STREAM_FIX_FORMAT;
689 flags |= PA_STREAM_NO_REMIX_CHANNELS;
693 flags |= PA_STREAM_NO_REMAP_CHANNELS;
697 if (((latency = (size_t) atoi(optarg))) <= 0) {
698 fprintf(stderr, _("Invalid latency specification '%s'\n"), optarg);
703 case ARG_PROCESS_TIME:
704 if (((process_time = (size_t) atoi(optarg))) <= 0) {
705 fprintf(stderr, _("Invalid process time specification '%s'\n"), optarg);
715 if (!pa_sample_spec_valid(&sample_spec)) {
716 fprintf(stderr, _("Invalid sample specification\n"));
720 if (channel_map_set && pa_channel_map_compatible(&channel_map, &sample_spec)) {
721 fprintf(stderr, _("Channel map doesn't match sample specification\n"));
726 char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
727 pa_sample_spec_snprint(t, sizeof(t), &sample_spec);
728 fprintf(stderr, _("Opening a %s stream with sample specification '%s'.\n"), mode == RECORD ? _("recording") : _("playback"), t);
731 if (!(optind >= argc)) {
732 if (optind+1 == argc) {
735 if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) {
736 fprintf(stderr, _("open(): %s\n"), strerror(errno));
740 if (dup2(fd, mode == PLAYBACK ? 0 : 1) < 0) {
741 fprintf(stderr, _("dup2(): %s\n"), strerror(errno));
748 stream_name = pa_xstrdup(argv[optind]);
751 fprintf(stderr, _("Too many arguments.\n"));
757 client_name = pa_xstrdup(bn);
760 stream_name = pa_xstrdup(client_name);
762 /* Set up a new main loop */
763 if (!(m = pa_mainloop_new())) {
764 fprintf(stderr, _("pa_mainloop_new() failed.\n"));
768 mainloop_api = pa_mainloop_get_api(m);
770 r = pa_signal_init(mainloop_api);
772 pa_signal_new(SIGINT, exit_signal_callback, NULL);
773 pa_signal_new(SIGTERM, exit_signal_callback, NULL);
775 pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL);
778 signal(SIGPIPE, SIG_IGN);
781 if (!(stdio_event = mainloop_api->io_new(mainloop_api,
782 mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO,
783 mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT,
784 mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) {
785 fprintf(stderr, _("io_new() failed.\n"));
789 /* Create a new connection context */
790 if (!(context = pa_context_new(mainloop_api, client_name))) {
791 fprintf(stderr, _("pa_context_new() failed.\n"));
795 pa_context_set_state_callback(context, context_state_callback, NULL);
797 /* Connect the context */
798 if (pa_context_connect(context, server, 0, NULL) < 0) {
799 fprintf(stderr, _("pa_context_connect() failed: %s\n"), pa_strerror(pa_context_errno(context)));
806 pa_gettimeofday(&tv);
807 pa_timeval_add(&tv, TIME_EVENT_USEC);
809 if (!(time_event = mainloop_api->time_new(mainloop_api, &tv, time_event_callback, NULL))) {
810 fprintf(stderr, _("time_new() failed.\n"));
815 /* Run the main loop */
816 if (pa_mainloop_run(m, &ret) < 0) {
817 fprintf(stderr, _("pa_mainloop_run() failed.\n"));
823 pa_stream_unref(stream);
826 pa_context_unref(context);
829 assert(mainloop_api);
830 mainloop_api->io_free(stdio_event);
834 assert(mainloop_api);
835 mainloop_api->time_free(time_event);
847 pa_xfree(client_name);
848 pa_xfree(stream_name);