X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/c4e276edbd84cbb8c5b594c9f427b0a25a7fb2ab..f975aad1d3563821f255d116a4c7b4005d7f2a02:/src/utils/pacat.c diff --git a/src/utils/pacat.c b/src/utils/pacat.c index d348c16a..e60dfcac 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -37,14 +37,15 @@ #include -#include #include #include -#include #include +#include #include +#include #include +#include #define TIME_EVENT_USEC 50000 @@ -59,6 +60,9 @@ static pa_mainloop_api *mainloop_api = NULL; static void *buffer = NULL; static size_t buffer_length = 0, buffer_index = 0; +static void *silence_buffer = NULL; +static size_t silence_buffer_length = 0; + static pa_io_event* stdio_event = NULL; static pa_proplist *proplist = NULL; @@ -66,19 +70,19 @@ static char *device = NULL; static SNDFILE* sndfile = NULL; -static pa_bool_t verbose = FALSE; +static bool verbose = false; static pa_volume_t volume = PA_VOLUME_NORM; -static pa_bool_t volume_is_set = FALSE; +static bool volume_is_set = false; static pa_sample_spec sample_spec = { .format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2 }; -static pa_bool_t sample_spec_set = FALSE; +static bool sample_spec_set = false; static pa_channel_map channel_map; -static pa_bool_t channel_map_set = FALSE; +static bool channel_map_set = false; static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL; static sf_count_t (*writef_function)(SNDFILE *_sndfile, const void *ptr, sf_count_t frames) = NULL; @@ -86,10 +90,15 @@ static sf_count_t (*writef_function)(SNDFILE *_sndfile, const void *ptr, sf_coun static pa_stream_flags_t flags = 0; static size_t latency = 0, process_time = 0; +static int32_t latency_msec = 0, process_time_msec = 0; -static pa_bool_t raw = TRUE; +static bool raw = true; static int file_format = -1; +static uint32_t monitor_stream = PA_INVALID_INDEX; + +static uint32_t cork_requests = 0; + /* A shortcut for terminating the application */ static void quit(int ret) { pa_assert(mainloop_api); @@ -103,6 +112,7 @@ static void context_drain_complete(pa_context*c, void *userdata) { /* Stream draining complete */ static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + pa_operation *o = NULL; if (!success) { pa_log(_("Failed to drain stream: %s"), pa_strerror(pa_context_errno(context))); @@ -116,9 +126,10 @@ static void stream_drain_complete(pa_stream*s, int success, void *userdata) { pa_stream_unref(stream); stream = NULL; - if (!pa_context_drain(context, context_drain_complete, NULL)) + if (!(o = pa_context_drain(context, context_drain_complete, NULL))) pa_context_disconnect(context); else { + pa_operation_unref(o); if (verbose) pa_log(_("Draining connection to server.")); } @@ -193,28 +204,41 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { pa_assert(sndfile); - if (pa_stream_begin_write(s, &data, &length) < 0) { - pa_log(_("pa_stream_begin_write() failed: %s"), pa_strerror(pa_context_errno(context))); - quit(1); - return; - } + for (;;) { + size_t data_length = length; - if (readf_function) { - size_t k = pa_frame_size(&sample_spec); + if (pa_stream_begin_write(s, &data, &data_length) < 0) { + pa_log(_("pa_stream_begin_write() failed: %s"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } - if ((bytes = readf_function(sndfile, data, (sf_count_t) (length/k))) > 0) - bytes *= (sf_count_t) k; + if (readf_function) { + size_t k = pa_frame_size(&sample_spec); - } else - bytes = sf_read_raw(sndfile, data, (sf_count_t) length); + if ((bytes = readf_function(sndfile, data, (sf_count_t) (data_length/k))) > 0) + bytes *= (sf_count_t) k; - if (bytes > 0) - pa_stream_write(s, data, (size_t) bytes, NULL, 0, PA_SEEK_RELATIVE); - else - pa_stream_cancel_write(s); + } else + bytes = sf_read_raw(sndfile, data, (sf_count_t) data_length); - if (bytes < (sf_count_t) length) - start_drain(); + if (bytes > 0) + pa_stream_write(s, data, (size_t) bytes, NULL, 0, PA_SEEK_RELATIVE); + else + pa_stream_cancel_write(s); + + /* EOF? */ + if (bytes < (sf_count_t) data_length) { + start_drain(); + break; + } + + /* Request fulfilled */ + if ((size_t) bytes >= length) + break; + + length -= bytes; + } } } @@ -239,18 +263,18 @@ static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { return; } - pa_assert(data); pa_assert(length > 0); - if (buffer) { + /* If there is a hole in the stream, we generate silence, except + * if it's a passthrough stream in which case we skip the hole. */ + if (data || !(flags & PA_STREAM_PASSTHROUGH)) { buffer = pa_xrealloc(buffer, buffer_length + length); - memcpy((uint8_t*) buffer + buffer_length, data, length); + if (data) + memcpy((uint8_t *) buffer + buffer_length, data, length); + else + pa_silence_memory((uint8_t *) buffer + buffer_length, length, &sample_spec); + buffer_length += length; - } else { - buffer = pa_xmalloc(length); - memcpy(buffer, data, length); - buffer_length = length; - buffer_index = 0; } pa_stream_drop(s); @@ -269,17 +293,27 @@ static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { return; } - pa_assert(data); pa_assert(length > 0); + if (!data && (flags & PA_STREAM_PASSTHROUGH)) { + pa_stream_drop(s); + continue; + } + + if (!data && length > silence_buffer_length) { + silence_buffer = pa_xrealloc(silence_buffer, length); + pa_silence_memory((uint8_t *) silence_buffer + silence_buffer_length, length - silence_buffer_length, &sample_spec); + silence_buffer_length = length; + } + if (writef_function) { size_t k = pa_frame_size(&sample_spec); - if ((bytes = writef_function(sndfile, data, (sf_count_t) (length/k))) > 0) + if ((bytes = writef_function(sndfile, data ? data : silence_buffer, (sf_count_t) (length/k))) > 0) bytes *= (sf_count_t) k; } else - bytes = sf_write_raw(sndfile, data, (sf_count_t) length); + bytes = sf_write_raw(sndfile, data ? data : silence_buffer, (sf_count_t) length); if (bytes < (sf_count_t) length) quit(1); @@ -322,10 +356,10 @@ static void stream_state_callback(pa_stream *s, void *userdata) { pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)), pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s))); - pa_log(_("Connected to device %s (%u, %ssuspended)."), + pa_log(_("Connected to device %s (index: %u, suspended: %s)."), pa_stream_get_device_name(s), pa_stream_get_device_index(s), - pa_stream_is_suspended(s) ? "" : "not "); + pa_yes_no(pa_stream_is_suspended(s))); } break; @@ -392,6 +426,24 @@ static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *p t = pa_proplist_to_string_sep(pl, ", "); pa_log("Got event '%s', properties '%s'", name, t); + + if (pa_streq(name, PA_STREAM_EVENT_REQUEST_CORK)) { + if (cork_requests == 0) { + pa_log(_("Cork request stack is empty: corking stream")); + pa_operation_unref(pa_stream_cork(s, 1, NULL, NULL)); + } + cork_requests++; + } else if (pa_streq(name, PA_STREAM_EVENT_REQUEST_UNCORK)) { + if (cork_requests == 1) { + pa_log(_("Cork request stack is empty: uncorking stream")); + pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL)); + } + if (cork_requests == 0) + pa_log(_("Warning: Received more uncork requests than cork requests!")); + else + cork_requests--; + } + pa_xfree(t); } @@ -434,30 +486,39 @@ static void context_state_callback(pa_context *c, void *userdata) { buffer_attr.maxlength = (uint32_t) -1; buffer_attr.prebuf = (uint32_t) -1; - if (latency > 0) { + if (latency_msec > 0) { + buffer_attr.fragsize = buffer_attr.tlength = pa_usec_to_bytes(latency_msec * PA_USEC_PER_MSEC, &sample_spec); + flags |= PA_STREAM_ADJUST_LATENCY; + } else if (latency > 0) { buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) latency; - buffer_attr.minreq = (uint32_t) process_time; flags |= PA_STREAM_ADJUST_LATENCY; - } else { - buffer_attr.tlength = (uint32_t) -1; + } else + buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) -1; + + if (process_time_msec > 0) { + buffer_attr.minreq = pa_usec_to_bytes(process_time_msec * PA_USEC_PER_MSEC, &sample_spec); + } else if (process_time > 0) + buffer_attr.minreq = (uint32_t) process_time; + else buffer_attr.minreq = (uint32_t) -1; - buffer_attr.fragsize = (uint32_t) -1; - } if (mode == PLAYBACK) { pa_cvolume cv; - if (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) { + if (pa_stream_connect_playback(stream, device, &buffer_attr, flags, volume_is_set ? pa_cvolume_set(&cv, sample_spec.channels, volume) : NULL, NULL) < 0) { pa_log(_("pa_stream_connect_playback() failed: %s"), pa_strerror(pa_context_errno(c))); goto fail; } } else { - if (pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : NULL, flags) < 0) { + if (monitor_stream != PA_INVALID_INDEX && (pa_stream_set_monitor_stream(stream, monitor_stream) < 0)) { + pa_log(_("Failed to set monitor stream: %s"), pa_strerror(pa_context_errno(c))); + goto fail; + } + if (pa_stream_connect_record(stream, device, &buffer_attr, flags) < 0) { pa_log(_("pa_stream_connect_record() failed: %s"), pa_strerror(pa_context_errno(c))); goto fail; } } - break; } @@ -497,7 +558,7 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even buffer = pa_xmalloc(l); - if ((r = read(fd, buffer, l)) <= 0) { + if ((r = pa_read(fd, buffer, l, userdata)) <= 0) { if (r == 0) { if (verbose) pa_log(_("Got EOF.")); @@ -536,7 +597,7 @@ static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_eve pa_assert(buffer_length); - if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) { + if ((r = pa_write(fd, (uint8_t*) buffer+buffer_index, buffer_length, userdata)) <= 0) { pa_log(_("write() failed: %s"), strerror(errno)); quit(1); @@ -555,7 +616,7 @@ static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_eve } } -/* UNIX signal to quit recieved */ +/* UNIX signal to quit received */ static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { if (verbose) pa_log(_("Got signal, exiting.")); @@ -583,6 +644,7 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd fprintf(stderr, " \r"); } +#ifdef SIGUSR1 /* Someone requested that the latency is shown */ static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { @@ -591,6 +653,7 @@ static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int s pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)); } +#endif static void time_event_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) { @@ -624,20 +687,24 @@ static void help(const char *argv0) { " --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n" " (defaults to 2)\n" " --channel-map=CHANNELMAP Channel map to use instead of the default\n" - " --fix-format Take the sample format from the sink the stream is\n" + " --fix-format Take the sample format from the sink/source the stream is\n" " being connected to.\n" - " --fix-rate Take the sampling rate from the sink the stream is\n" + " --fix-rate Take the sampling rate from the sink/source the stream is\n" " being connected to.\n" " --fix-channels Take the number of channels and the channel map\n" - " from the sink the stream is being connected to.\n" + " from the sink/source the stream is being connected to.\n" " --no-remix Don't upmix or downmix channels.\n" " --no-remap Map channels by index instead of name.\n" " --latency=BYTES Request the specified latency in bytes.\n" " --process-time=BYTES Request the specified process time per request in bytes.\n" + " --latency-msec=MSEC Request the specified latency in msec.\n" + " --process-time-msec=MSEC Request the specified process time per request in msec.\n" " --property=PROPERTY=VALUE Set the specified property to the specified value.\n" " --raw Record/play raw PCM data.\n" - " --file-format=FFORMAT Record/play formatted PCM data.\n" - " --list-file-formats List available file formats.\n") + " --passthrough Passthrough data.\n" + " --file-format[=FFORMAT] Record/play formatted PCM data.\n" + " --list-file-formats List available file formats.\n" + " --monitor-stream=INDEX Record from the sink input with index INDEX.\n") , argv0); } @@ -657,9 +724,13 @@ enum { ARG_LATENCY, ARG_PROCESS_TIME, ARG_RAW, + ARG_PASSTHROUGH, ARG_PROPERTY, ARG_FILE_FORMAT, - ARG_LIST_FILE_FORMATS + ARG_LIST_FILE_FORMATS, + ARG_LATENCY_MSEC, + ARG_PROCESS_TIME_MSEC, + ARG_MONITOR_STREAM, }; int main(int argc, char *argv[]) { @@ -668,6 +739,8 @@ int main(int argc, char *argv[]) { char *bn, *server = NULL; pa_time_event *time_event = NULL; const char *filename = NULL; + /* type for pa_read/_write. passed as userdata to the callbacks */ + unsigned long type = 0; static const struct option long_options[] = { {"record", 0, NULL, 'r'}, @@ -693,28 +766,34 @@ int main(int argc, char *argv[]) { {"process-time", 1, NULL, ARG_PROCESS_TIME}, {"property", 1, NULL, ARG_PROPERTY}, {"raw", 0, NULL, ARG_RAW}, + {"passthrough", 0, NULL, ARG_PASSTHROUGH}, {"file-format", 2, NULL, ARG_FILE_FORMAT}, {"list-file-formats", 0, NULL, ARG_LIST_FILE_FORMATS}, + {"latency-msec", 1, NULL, ARG_LATENCY_MSEC}, + {"process-time-msec", 1, NULL, ARG_PROCESS_TIME_MSEC}, + {"monitor-stream", 1, NULL, ARG_MONITOR_STREAM}, {NULL, 0, NULL, 0} }; setlocale(LC_ALL, ""); +#ifdef ENABLE_NLS bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); +#endif bn = pa_path_get_filename(argv[0]); if (strstr(bn, "play")) { mode = PLAYBACK; - raw = FALSE; + raw = false; } else if (strstr(bn, "record")) { mode = RECORD; - raw = FALSE; + raw = false; } else if (strstr(bn, "cat")) { mode = PLAYBACK; - raw = TRUE; - } if (strstr(bn, "rec") || strstr(bn, "mon")) { + raw = true; + } else if (strstr(bn, "rec") || strstr(bn, "mon")) { mode = RECORD; - raw = TRUE; + raw = true; } proplist = pa_proplist_new(); @@ -792,23 +871,23 @@ int main(int argc, char *argv[]) { case ARG_VOLUME: { int v = atoi(optarg); volume = v < 0 ? 0U : (pa_volume_t) v; - volume_is_set = TRUE; + volume_is_set = true; break; } case ARG_CHANNELS: sample_spec.channels = (uint8_t) atoi(optarg); - sample_spec_set = TRUE; + sample_spec_set = true; break; case ARG_SAMPLEFORMAT: sample_spec.format = pa_parse_sample_format(optarg); - sample_spec_set = TRUE; + sample_spec_set = true; break; case ARG_SAMPLERATE: sample_spec.rate = (uint32_t) atoi(optarg); - sample_spec_set = TRUE; + sample_spec_set = true; break; case ARG_CHANNELMAP: @@ -817,7 +896,7 @@ int main(int argc, char *argv[]) { goto quit; } - channel_map_set = TRUE; + channel_map_set = true; break; case ARG_FIX_CHANNELS: @@ -854,6 +933,20 @@ int main(int argc, char *argv[]) { } break; + case ARG_LATENCY_MSEC: + if (((latency_msec = (int32_t) atoi(optarg))) <= 0) { + pa_log(_("Invalid latency specification '%s'"), optarg); + goto quit; + } + break; + + case ARG_PROCESS_TIME_MSEC: + if (((process_time_msec = (int32_t) atoi(optarg))) <= 0) { + pa_log(_("Invalid process time specification '%s'"), optarg); + goto quit; + } + break; + case ARG_PROPERTY: { char *t; @@ -870,12 +963,14 @@ int main(int argc, char *argv[]) { } case ARG_RAW: - raw = TRUE; + raw = true; break; - case ARG_FILE_FORMAT: - raw = FALSE; + case ARG_PASSTHROUGH: + flags |= PA_STREAM_PASSTHROUGH; + break; + case ARG_FILE_FORMAT: if (optarg) { if ((file_format = pa_sndfile_format_from_string(optarg)) < 0) { pa_log(_("Unknown file format %s."), optarg); @@ -883,7 +978,7 @@ int main(int argc, char *argv[]) { } } - raw = FALSE; + raw = false; break; case ARG_LIST_FILE_FORMATS: @@ -891,6 +986,13 @@ int main(int argc, char *argv[]) { ret = 0; goto quit; + case ARG_MONITOR_STREAM: + if (pa_atou(optarg, &monitor_stream) < 0) { + pa_log(_("Failed to parse the argument for --monitor-stream")); + goto quit; + } + break; + default: goto quit; } @@ -934,13 +1036,19 @@ int main(int argc, char *argv[]) { goto quit; } - /* Transparently upgrade classic .wav to wavex for multichannel audio */ if (file_format <= 0) { - if ((sample_spec.channels == 2 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_LEFT && - channel_map.map[1] == PA_CHANNEL_POSITION_RIGHT))) || - (sample_spec.channels == 1 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_MONO)))) + char *extension; + if (filename && (extension = strrchr(filename, '.'))) + file_format = pa_sndfile_format_from_string(extension+1); + if (file_format <= 0) file_format = SF_FORMAT_WAV; - else + /* Transparently upgrade classic .wav to wavex for multichannel audio */ + if (file_format == SF_FORMAT_WAV && + (sample_spec.channels > 2 || + (channel_map_set && + !(sample_spec.channels == 1 && channel_map.map[0] == PA_CHANNEL_POSITION_MONO) && + !(sample_spec.channels == 2 && channel_map.map[0] == PA_CHANNEL_POSITION_LEFT + && channel_map.map[1] == PA_CHANNEL_POSITION_RIGHT)))) file_format = SF_FORMAT_WAVEX; } @@ -962,7 +1070,7 @@ int main(int argc, char *argv[]) { pa_log(_("Failed to determine sample specification from file.")); goto quit; } - sample_spec_set = TRUE; + sample_spec_set = true; if (!channel_map_set) { /* Allow the user to overwrite the channel map on the command line */ @@ -970,7 +1078,7 @@ int main(int argc, char *argv[]) { if (sample_spec.channels > 2) pa_log(_("Warning: Failed to determine channel map from file.")); } else - channel_map_set = TRUE; + channel_map_set = true; } } } @@ -1028,6 +1136,11 @@ int main(int argc, char *argv[]) { if ((t = filename) || (t = pa_proplist_gets(proplist, PA_PROP_APPLICATION_NAME))) pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t); + + if (!pa_proplist_contains(proplist, PA_PROP_MEDIA_NAME)) { + pa_log(_("Failed to set media name.")); + goto quit; + } } /* Set up a new main loop */ @@ -1047,10 +1160,14 @@ int main(int argc, char *argv[]) { pa_disable_sigpipe(); if (raw) { +#ifdef OS_IS_WIN32 + /* need to turn on binary mode for stdio io. Windows, meh */ + setmode(mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, O_BINARY); +#endif if (!(stdio_event = mainloop_api->io_new(mainloop_api, mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, - mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { + mode == PLAYBACK ? stdin_callback : stdout_callback, &type))) { pa_log(_("io_new() failed.")); goto quit; } @@ -1105,6 +1222,7 @@ quit: pa_mainloop_free(m); } + pa_xfree(silence_buffer); pa_xfree(buffer); pa_xfree(server);