diff options
35 files changed, 1773 insertions, 1047 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index b6c98fc3..a7ec6917 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -177,7 +177,6 @@ endif bin_PROGRAMS += \ pacat \ pactl \ - paplay \ pasuspender if HAVE_AF_UNIX @@ -195,23 +194,18 @@ endif bin_SCRIPTS = esdcompat start-pulseaudio-x11 pacat_SOURCES = utils/pacat.c -pacat_LDADD = $(AM_LDADD) libpulse.la -pacat_CFLAGS = $(AM_CFLAGS) +pacat_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) +pacat_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -paplay_SOURCES = utils/paplay.c -paplay_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) -paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) -paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) - pactl_SOURCES = utils/pactl.c pactl_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) pactl_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) pactl_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) pasuspender_SOURCES = utils/pasuspender.c -pasuspender_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la $(LIBSNDFILE_LIBS) -pasuspender_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) +pasuspender_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@.la +pasuspender_CFLAGS = $(AM_CFLAGS) pasuspender_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) pacmd_SOURCES = utils/pacmd.c @@ -225,7 +219,7 @@ pax11publish_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINORMICRO@ pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) pabrowse_SOURCES = utils/pabrowse.c -pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la +pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la libpulsecommon-@PA_MAJORMINORMICRO@.la pabrowse_CFLAGS = $(AM_CFLAGS) pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) @@ -603,11 +597,12 @@ libpulsecommon_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/tagstruct.c pulsecore/tagstruct.h \ pulsecore/time-smoother.c pulsecore/time-smoother.h \ pulsecore/tokenizer.c pulsecore/tokenizer.h \ + pulsecore/sndfile-util.c pulsecore/sndfile-util.h \ pulsecore/winsock.h libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) libpulsecommon_@PA_MAJORMINORMICRO@_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version -libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) +libpulsecommon_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) # proplist-util.h uses these header files, but not the library itself! libpulsecommon_@PA_MAJORMINORMICRO@_la_CFLAGS += $(GLIB20_CFLAGS) $(GTK20_CFLAGS) @@ -821,9 +816,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES = \ pulsecore/time-smoother.c pulsecore/time-smoother.h \ pulsecore/database.h -libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS = $(AM_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSPEEX_CFLAGS) $(WINSOCK_CFLAGS) $(LIBOIL_CFLAGS) libpulsecore_@PA_MAJORMINORMICRO@_la_LDFLAGS = -avoid-version -libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libpulsecore-foreign.la +libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSPEEX_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LTLIBICONV) libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libpulsecore-foreign.la if HAVE_X11 libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/x11wrap.c pulsecore/x11wrap.h @@ -1643,11 +1638,17 @@ install-exec-hook: chmod u+s $(DESTDIR)$(bindir)/pulseaudio -chmod u+s $(DESTDIR)$(pulselibexecdir)/proximity-helper ln -sf pacat $(DESTDIR)$(bindir)/parec + ln -sf pacat $(DESTDIR)$(bindir)/pamon + ln -sf pacat $(DESTDIR)$(bindir)/paplay + ln -sf pacat $(DESTDIR)$(bindir)/parecord rm -f $(DESTDIR)$(libdir)/libpulsedsp.la rm -f $(DESTDIR)$(modlibexecdir)/*.la uninstall-hook: rm -f $(DESTDIR)$(bindir)/parec + rm -f $(DESTDIR)$(bindir)/pamon + rm -f $(DESTDIR)$(bindir)/paplay + rm -f $(DESTDIR)$(bindir)/parecord rm -f $(DESTDIR)$(libdir)/libpulsedsp.* rm -f $(DESTDIR)$(modlibexecdir)/*.so diff --git a/src/daemon/main.c b/src/daemon/main.c index a2324516..3e50baad 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -946,9 +946,7 @@ int main(int argc, char *argv[]) { valid_pid_file = TRUE; } -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif + pa_disable_sigpipe(); if (pa_rtclock_hrtimer()) pa_log_info(_("Fresh high-resolution timers available! Bon appetit!")); diff --git a/src/map-file b/src/map-file index d0102ae0..c46c6792 100644 --- a/src/map-file +++ b/src/map-file @@ -15,11 +15,13 @@ pa_channel_map_can_balance; pa_channel_map_can_fade; pa_channel_map_compatible; pa_channel_map_equal; +pa_channel_map_has_position; pa_channel_map_init; pa_channel_map_init_auto; pa_channel_map_init_extend; pa_channel_map_init_mono; pa_channel_map_init_stereo; +pa_channel_map_mask; pa_channel_map_parse; pa_channel_map_snprint; pa_channel_map_superset; @@ -110,19 +112,24 @@ pa_context_suspend_source_by_name; pa_context_unload_module; pa_context_unref; pa_cvolume_avg; +pa_cvolume_avg_mask; pa_cvolume_channels_equal_to; pa_cvolume_compatible; pa_cvolume_compatible_with_channel_map; pa_cvolume_equal; pa_cvolume_get_balance; pa_cvolume_get_fade; +pa_cvolume_get_position; pa_cvolume_init; pa_cvolume_max; +pa_cvolume_max_mask; pa_cvolume_remap; pa_cvolume_scale; +pa_cvolume_scale_mask; pa_cvolume_set; pa_cvolume_set_balance; pa_cvolume_set_fade; +pa_cvolume_set_position; pa_cvolume_snprint; pa_cvolume_valid; pa_ext_stream_restore_delete; @@ -175,6 +182,7 @@ pa_proplist_iterate; pa_proplist_new; pa_proplist_set; pa_proplist_setf; +pa_proplist_setp; pa_proplist_sets; pa_proplist_size; pa_proplist_to_string; @@ -182,6 +190,8 @@ pa_proplist_to_string_sep; pa_proplist_unset; pa_proplist_unset_many; pa_proplist_update; +pa_sample_format_is_be; +pa_sample_format_is_le; pa_sample_format_to_string; pa_sample_size; pa_sample_size_of_format; diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 51d466e1..d894b9ce 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -135,7 +135,7 @@ static void enumerate_cb( bonus += 20000; } - pa_log_info("Found output profile '%s'", t); + pa_log_info("Found profile '%s'", t); p = pa_card_profile_new(n, t, sizeof(struct profile_data)); diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index d9bab6bd..c0c2facf 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -54,12 +54,12 @@ PA_MODULE_DESCRIPTION("Clocked NULL sink"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( + "sink_name=<name of sink> " + "sink_properties=<properties for the sink> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name of sink> " - "channel_map=<channel map> " - "description=<description for the sink>"); + "channels=<number of channels> " + "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "null" #define BLOCK_USEC (PA_USEC_PER_SEC * 2) @@ -78,12 +78,13 @@ struct userdata { }; static const char* const valid_modargs[] = { - "rate", + "sink_name", + "sink_properties", "format", + "rate", "channels", - "sink_name", "channel_map", - "description", + "description", /* supported for compatibility reasons, made redundant by sink_properties= */ NULL }; @@ -289,6 +290,12 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { + pa_log("Invalid properties."); + pa_sink_new_data_done(&data); + goto fail; + } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&data); diff --git a/src/modules/module-rygel-media-server.c b/src/modules/module-rygel-media-server.c index 080b612f..4c02e958 100644 --- a/src/modules/module-rygel-media-server.c +++ b/src/modules/module-rygel-media-server.c @@ -57,18 +57,18 @@ PA_MODULE_USAGE( /* This implements http://live.gnome.org/Rygel/MediaServerSpec */ -#define SERVICE_NAME "org.Rygel.MediaServer1.PulseAudio" +#define SERVICE_NAME "org.gnome.UPnP.MediaServer1.PulseAudio" -#define OBJECT_ROOT "/org/Rygel/MediaServer1/PulseAudio" -#define OBJECT_SINKS "/org/Rygel/MediaServer1/PulseAudio/Sinks" -#define OBJECT_SOURCES "/org/Rygel/MediaServer1/PulseAudio/Sources" +#define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio" +#define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks" +#define OBJECT_SOURCES "/org/gnome/UPnP/MediaServer1/PulseAudio/Sources" #define CONTAINER_INTROSPECT_XML_PREFIX \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "<node>" \ " <!-- If you are looking for documentation make sure to check out" \ " http://live.gnome.org/Rygel/MediaServerSpec -->" \ - " <interface name=\"org.Rygel.MediaContainer1\">" \ + " <interface name=\"org.gnome.UPnP.MediaContainer1\">" \ " <signal name=\"Updated\">" \ " <arg name=\"path\" type=\"o\"/>" \ " </signal>" \ @@ -77,7 +77,7 @@ PA_MODULE_USAGE( " <property name=\"Containers\" type=\"ao\" access=\"read\"/>" \ " <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>" \ " </interface>" \ - " <interface name=\"org.Rygel.MediaObject1\">" \ + " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ " </interface>" \ @@ -112,12 +112,12 @@ PA_MODULE_USAGE( "<node>" \ " <!-- If you are looking for documentation make sure to check out" \ " http://live.gnome.org/Rygel/MediaProviderSpec -->" \ - " <interface name=\"org.Rygel.MediaItem1\">" \ + " <interface name=\"org.gnome.UPnP.MediaItem1\">" \ " <property name=\"URLs\" type=\"as\" access=\"read\"/>" \ " <property name=\"MIMEType\" type=\"s\" access=\"read\"/>" \ " <property name=\"Type\" type=\"s\" access=\"read\"/>" \ " </interface>" \ - " <interface name=\"org.Rygel.MediaObject1\">" \ + " <interface name=\"org.gnome.UPnP.MediaObject1\">" \ " <property name=\"Parent\" type=\"s\" access=\"read\"/>" \ " <property name=\"DisplayName\" type=\"s\" access=\"read\"/>" \ " </interface>" \ @@ -174,7 +174,7 @@ static void send_signal(struct userdata *u, pa_source *s) { else parent = OBJECT_SOURCES; - pa_assert_se(m = dbus_message_new_signal(parent, "org.Rygel.MediaContainer1", "Updated")); + pa_assert_se(m = dbus_message_new_signal(parent, "org.gnome.UPnP.MediaContainer1", "Updated")); pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL)); dbus_message_unref(m); @@ -349,23 +349,23 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u pa_assert(u); - if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Containers")) { + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers)); - } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ContainerCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers)); - } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Items")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); - } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ItemCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); - } else if (message_is_property_get_all(m, "org.Rygel.MediaContainer1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); @@ -378,15 +378,15 @@ static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *u append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children)); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "Parent")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object(r, NULL, OBJECT_ROOT); - } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "DisplayName")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, u->display_name); - } else if (message_is_property_get_all(m, "org.Rygel.MediaObject1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); @@ -509,15 +509,15 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag /* Container nodes */ - if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Containers")) { + if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children)); - } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ContainerCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children)); - } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Items")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) { char ** array; unsigned n; @@ -528,14 +528,14 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag free_child_array(array, n); - } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ItemCount")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_unsigned(r, NULL, pa_streq(path, OBJECT_SINKS) ? pa_idxset_size(u->core->sinks) : pa_idxset_size(u->core->sources)); - } else if (message_is_property_get_all(m, "org.Rygel.MediaContainer1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) { DBusMessageIter iter, sub; char **array; unsigned n; @@ -557,11 +557,11 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "Parent")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object(r, NULL, OBJECT_ROOT); - } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "DisplayName")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, @@ -569,7 +569,7 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag _("Output Devices") : _("Input Devices")); - } else if (message_is_property_get_all(m, "org.Rygel.MediaObject1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); @@ -632,15 +632,15 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag if (!sink && !source) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - if (message_is_property_get(m, "org.Rygel.MediaObject1", "Parent")) { + if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES); - } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "DisplayName")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); - } else if (message_is_property_get_all(m, "org.Rygel.MediaObject1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) { DBusMessageIter iter, sub; pa_assert_se(r = dbus_message_new_method_return(m)); @@ -651,11 +651,11 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION))); pa_assert_se(dbus_message_iter_close_container(&iter, &sub)); - } else if (message_is_property_get(m, "org.Rygel.MediaItem1", "Type")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) { pa_assert_se(r = dbus_message_new_method_return(m)); append_variant_string(r, NULL, "audio"); - } else if (message_is_property_get(m, "org.Rygel.MediaItem1", "MIMEType")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) { char *t; if (sink) @@ -667,7 +667,7 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag append_variant_string(r, NULL, t); pa_xfree(t); - } else if (message_is_property_get(m, "org.Rygel.MediaItem1", "URLs")) { + } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) { DBusMessageIter iter, sub, array; char *url; @@ -685,10 +685,10 @@ static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessag pa_xfree(url); - } else if (message_is_property_get_all(m, "org.Rygel.MediaItem1")) { + } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) { DBusMessageIter iter, sub, dict, variant, array; char *url, *t; - const char *un = "urls"; + const char *un = "URLs"; pa_assert_se(r = dbus_message_new_method_return(m)); dbus_message_iter_init_append(r, &iter); diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index ab26137f..d6f549cd 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -477,6 +477,7 @@ static void build_pollfd(struct userdata *u) { pollfd->revents = 0; } +/* Called from IO context */ static int suspend(struct userdata *u) { pa_assert(u); pa_assert(u->fd >= 0); @@ -526,6 +527,7 @@ static int suspend(struct userdata *u) { return 0; } +/* Called from IO context */ static int unsuspend(struct userdata *u) { int m; pa_sample_spec ss, *ss_original; @@ -616,10 +618,10 @@ static int unsuspend(struct userdata *u) { build_pollfd(u); - if (u->sink) - pa_sink_get_volume(u->sink, TRUE, FALSE); - if (u->source) - pa_source_get_volume(u->source, TRUE); + if (u->sink && u->sink->get_volume) + u->sink->get_volume(u->sink); + if (u->source && u->source->get_volume) + u->source->get_volume(u->source); pa_log_info("Resumed successfully..."); @@ -631,6 +633,7 @@ fail: return -1; } +/* Called from IO context */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; int ret; diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c index fe14df24..4654a9ad 100644 --- a/src/pulse/channelmap.c +++ b/src/pulse/channelmap.c @@ -852,3 +852,15 @@ int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t return 0; } + +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) { + unsigned c; + pa_channel_position_mask_t r = 0; + + pa_return_val_if_fail(pa_channel_map_valid(map), 0); + + for (c = 0; c < map->channels; c++) + r |= PA_CHANNEL_POSITION_MASK(map->map[c]); + + return r; +} diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h index a2925c1e..2aaead01 100644 --- a/src/pulse/channelmap.h +++ b/src/pulse/channelmap.h @@ -74,26 +74,30 @@ typedef enum pa_channel_position { PA_CHANNEL_POSITION_INVALID = -1, PA_CHANNEL_POSITION_MONO = 0, - PA_CHANNEL_POSITION_LEFT, - PA_CHANNEL_POSITION_RIGHT, - PA_CHANNEL_POSITION_CENTER, + PA_CHANNEL_POSITION_FRONT_LEFT, /* Apple calls this 'Left' */ + PA_CHANNEL_POSITION_FRONT_RIGHT, /* Apple calls this 'Right' */ + PA_CHANNEL_POSITION_FRONT_CENTER, /* Apple calls this 'Center' */ - PA_CHANNEL_POSITION_FRONT_LEFT = PA_CHANNEL_POSITION_LEFT, - PA_CHANNEL_POSITION_FRONT_RIGHT = PA_CHANNEL_POSITION_RIGHT, - PA_CHANNEL_POSITION_FRONT_CENTER = PA_CHANNEL_POSITION_CENTER, +/** \cond fulldocs */ + PA_CHANNEL_POSITION_LEFT = PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_RIGHT = PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_CENTER = PA_CHANNEL_POSITION_FRONT_CENTER, +/** \endcond */ - PA_CHANNEL_POSITION_REAR_CENTER, - PA_CHANNEL_POSITION_REAR_LEFT, - PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_REAR_CENTER, /* Microsoft calls this 'Back Center', Apple calls this 'Center Surround' */ + PA_CHANNEL_POSITION_REAR_LEFT, /* Microsoft calls this 'Back Left', Apple calls this 'Left Surround' */ + PA_CHANNEL_POSITION_REAR_RIGHT, /* Microsoft calls this 'Back Right', Apple calls this 'Right Surround' */ - PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_LFE, /* Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */ +/** \cond fulldocs */ PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE, +/** \endcond */ - PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, - PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, /* Apple calls this 'Left Center' */ + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, /* Apple calls this 'Right Center */ - PA_CHANNEL_POSITION_SIDE_LEFT, - PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_SIDE_LEFT, /* Apple calls this 'Left Surround Direct' */ + PA_CHANNEL_POSITION_SIDE_RIGHT, /* Apple calls this 'Right Surround Direct' */ PA_CHANNEL_POSITION_AUX0, PA_CHANNEL_POSITION_AUX1, @@ -128,15 +132,15 @@ typedef enum pa_channel_position { PA_CHANNEL_POSITION_AUX30, PA_CHANNEL_POSITION_AUX31, - PA_CHANNEL_POSITION_TOP_CENTER, + PA_CHANNEL_POSITION_TOP_CENTER, /* Apple calls this 'Top Center Surround' */ - PA_CHANNEL_POSITION_TOP_FRONT_LEFT, - PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, - PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, /* Apple calls this 'Vertical Height Left' */ + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, /* Apple calls this 'Vertical Height Right' */ + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, /* Apple calls this 'Vertical Height Center' */ - PA_CHANNEL_POSITION_TOP_REAR_LEFT, - PA_CHANNEL_POSITION_TOP_REAR_RIGHT, - PA_CHANNEL_POSITION_TOP_REAR_CENTER, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, /* Microsoft and Apple call this 'Top Back Left' */ + PA_CHANNEL_POSITION_TOP_REAR_RIGHT, /* Microsoft and Apple call this 'Top Back Right' */ + PA_CHANNEL_POSITION_TOP_REAR_CENTER, /* Microsoft and Apple call this 'Top Back Center' */ PA_CHANNEL_POSITION_MAX } pa_channel_position_t; @@ -201,6 +205,12 @@ typedef enum pa_channel_position { #define PA_CHANNEL_POSITION_MAX PA_CHANNEL_POSITION_MAX /** \endcond */ +/** A mask of channel positions. \since 0.9.16 */ +typedef uint64_t pa_channel_position_mask_t; + +/** Makes a bit mask from a channel position. \since 0.9.16 */ +#define PA_CHANNEL_POSITION_MASK(f) ((pa_channel_position_mask_t) (1 << (f))) + /** A list of channel mapping definitions for pa_channel_map_init_auto() */ typedef enum pa_channel_map_def { PA_CHANNEL_MAP_AIFF, @@ -329,6 +339,9 @@ const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) PA_GCC_PURE * least once in the channel map. \since 0.9.16 */ int pa_channel_map_has_position(const pa_channel_map *map, pa_channel_position_t p) PA_GCC_PURE; +/** Generates a bit mask from a channel map. \since 0.9.16 */ +pa_channel_position_mask_t pa_channel_map_mask(const pa_channel_map *map) PA_GCC_PURE; + PA_C_DECL_END #endif diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c index db4c9344..c904f533 100644 --- a/src/pulse/proplist.c +++ b/src/pulse/proplist.c @@ -140,6 +140,21 @@ static int proplist_setn(pa_proplist *p, const char *key, size_t key_length, con return 0; } +/** Will accept only valid UTF-8 */ +int pa_proplist_setp(pa_proplist *p, const char *pair) { + const char *t; + + pa_assert(p); + pa_assert(pair); + + if (!(t = strchr(pair, '='))) + return -1; + + return proplist_setn(p, + pair, t - pair, + t + 1, strchr(pair, 0) - t - 1); +} + static int proplist_sethex(pa_proplist *p, const char *key, size_t key_length, const char *value, size_t value_length) { struct property *prop; pa_bool_t add = FALSE; diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index 2e7e5ad0..4c791dce 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -39,6 +39,12 @@ PA_C_DECL_BEGIN /** For streams: localized media artist if applicable, formatted as UTF-8. e.g. "Guns'N'Roses" */ #define PA_PROP_MEDIA_ARTIST "media.artist" +/** For streams: localized media copyright string if applicable, formatted as UTF-8. e.g. "Evil Record Corp." */ +#define PA_PROP_MEDIA_COPYRIGHT "media.copyright" + +/** For streams: localized media generator software string if applicable, formatted as UTF-8. e.g. "Foocrop AudioFrobnicator" */ +#define PA_PROP_MEDIA_SOFTWARE "media.software" + /** For streams: media language if applicable, in standard POSIX format. e.g. "de_DE" */ #define PA_PROP_MEDIA_LANGUAGE "media.language" @@ -234,6 +240,14 @@ int pa_proplist_sets(pa_proplist *p, const char *key, const char *value); /** Append a new string entry to the property list, possibly * overwriting an already existing entry with the same key. An * internal copy of the data passed is made. Will accept only valid + * UTF-8. The string passed in must contain a '='. Left hand side of + * the '=' is used as key name, the right hand side as string + * data. \since 0.9.16 */ +int pa_proplist_setp(pa_proplist *p, const char *pair); + +/** Append a new string entry to the property list, possibly + * overwriting an already existing entry with the same key. An + * internal copy of the data passed is made. Will accept only valid * UTF-8. The data can be passed as printf()-style format string with * arguments. \since 0.9.11 */ int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) PA_GCC_PRINTF_ATTR(3,4); diff --git a/src/pulse/sample.c b/src/pulse/sample.c index ed7b1b08..0f19f8eb 100644 --- a/src/pulse/sample.c +++ b/src/pulse/sample.c @@ -241,3 +241,36 @@ pa_sample_format_t pa_parse_sample_format(const char *format) { return -1; } + +int pa_sample_format_is_le(pa_sample_format_t f) { + pa_assert(f >= PA_SAMPLE_U8); + pa_assert(f < PA_SAMPLE_MAX); + + switch (f) { + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_FLOAT32LE: + return 1; + + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_S24_32BE: + case PA_SAMPLE_FLOAT32BE: + return 0; + + default: + return -1; + } +} + +int pa_sample_format_is_be(pa_sample_format_t f) { + int r; + + if ((r = pa_sample_format_is_le(f)) < 0) + return r; + + return !r; +} diff --git a/src/pulse/sample.h b/src/pulse/sample.h index 138f13cf..53d7dea3 100644 --- a/src/pulse/sample.h +++ b/src/pulse/sample.h @@ -305,6 +305,26 @@ char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec); /** Pretty print a byte size value. (i.e. "2.5 MiB") */ char* pa_bytes_snprint(char *s, size_t l, unsigned v); +/** Return 1 when the specified format is little endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +int pa_sample_format_is_le(pa_sample_format_t f) PA_GCC_PURE; + +/** Return 1 when the specified format is big endian, return -1 when + * endianess does not apply to this format. \since 0.9.16 */ +int pa_sample_format_is_be(pa_sample_format_t f) PA_GCC_PURE; + +#ifdef WORDS_BIGENDIAN +#define pa_sample_format_is_ne(f) pa_sample_format_is_be(f) +#define pa_sample_format_is_re(f) pa_sample_format_is_le(f) +#else +/** Return 1 when the specified format is native endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +#define pa_sample_format_is_ne(f) pa_sample_format_is_le(f) +/** Return 1 when the specified format is reverse endian, return -1 + * when endianess does not apply to this format. \since 0.9.16 */ +#define pa_sample_format_is_re(f) pa_sample_format_is_be(f) +#endif + PA_C_DECL_END #endif diff --git a/src/pulse/simple.c b/src/pulse/simple.c index e70b7b1f..f4481fc3 100644 --- a/src/pulse/simple.c +++ b/src/pulse/simple.c @@ -50,35 +50,38 @@ struct pa_simple { int operation_success; }; -#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) do { \ -if (!(expression)) { \ - if (rerror) \ - *(rerror) = error; \ - return (ret); \ - } \ -} while(0); - -#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) do { \ -if (!(expression)) { \ - if (rerror) \ - *(rerror) = pa_context_errno((p)->context); \ - goto label; \ - } \ -} while(0); - -#define CHECK_DEAD_GOTO(p, rerror, label) do { \ -if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ - !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ - if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ - ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ - if (rerror) \ - *(rerror) = pa_context_errno((p)->context); \ - } else \ - if (rerror) \ - *(rerror) = PA_ERR_BADSTATE; \ - goto label; \ - } \ -} while(0); +#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = error; \ + return (ret); \ + } \ + } while(FALSE); + +#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + goto label; \ + } \ + } while(FALSE); + +#define CHECK_DEAD_GOTO(p, rerror, label) \ + do { \ + if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ + !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ + if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ + ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + } else \ + if (rerror) \ + *(rerror) = PA_ERR_BADSTATE; \ + goto label; \ + } \ + } while(FALSE); static void context_state_cb(pa_context *c, void *userdata) { pa_simple *p = userdata; @@ -198,9 +201,15 @@ pa_simple* pa_simple_new( pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p); if (dir == PA_STREAM_PLAYBACK) - r = pa_stream_connect_playback(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + r = pa_stream_connect_playback(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); else - r = pa_stream_connect_record(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE); + r = pa_stream_connect_record(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE); if (r < 0) { error = pa_context_errno(p->context); diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in index 566dd55e..3143e98e 100644 --- a/src/pulse/version.h.in +++ b/src/pulse/version.h.in @@ -60,6 +60,13 @@ const char* pa_get_library_version(void); /** The micro version of PA. \since 0.9.15 */ #define PA_MICRO @PA_MICRO@ +/** Evaluates to TRUE if the PulseAudio library version is equal or + * newer than the specified. \since 0.9.16 */ +#define PA_CHECK_VERSION(major,minor,micro) \ + ((PA_MAJOR > (major)) || \ + (PA_MAJOR == (major) && CA_MINOR > (minor)) || \ + (PA_MAJOR == (major) && CA_MINOR == (minor) && CA_MICRO >= (micro))) + PA_C_DECL_END #endif diff --git a/src/pulse/volume.c b/src/pulse/volume.c index a9622e78..64688e0b 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -80,29 +80,78 @@ pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { pa_volume_t pa_cvolume_avg(const pa_cvolume *a) { uint64_t sum = 0; - int i; + unsigned c; pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); - for (i = 0; i < a->channels; i++) - sum += a->values[i]; + for (c = 0; c < a->channels; c++) + sum += a->values[c]; sum /= a->channels; return (pa_volume_t) sum; } +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + uint64_t sum = 0; + unsigned c, n; + + pa_assert(a); + + if (!cm) + return pa_cvolume_avg(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = n = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + sum += a->values[c]; + n ++; + } + + if (n > 0) + sum /= n; + + return (pa_volume_t) sum; +} + pa_volume_t pa_cvolume_max(const pa_cvolume *a) { pa_volume_t m = 0; - int i; + unsigned c; pa_assert(a); pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED); - for (i = 0; i < a->channels; i++) - if (a->values[i] > m) - m = a->values[i]; + for (c = 0; c < a->channels; c++) + if (a->values[c] > m) + m = a->values[c]; + + return m; +} + +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) { + pa_volume_t m = 0; + unsigned c, n; + + pa_assert(a); + + if (!cm) + return pa_cvolume_max(a); + + pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED); + + for (c = n = 0; c < a->channels; c++) { + + if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask)) + continue; + + if (a->values[c] > m) + m = a->values[c]; + } return m; } @@ -120,20 +169,28 @@ pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) { return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) / v); } -#define USER_DECIBEL_RANGE 90 +/* Amplitude, not power */ +static double linear_to_dB(double v) { + return 20.0 * log10(v); +} + +static double dB_to_linear(double v) { + return pow(10.0, v / 20.0); +} pa_volume_t pa_sw_volume_from_dB(double dB) { - if (isinf(dB) < 0 || dB <= -USER_DECIBEL_RANGE) + if (isinf(dB) < 0 || dB <= PA_DECIBEL_MININFTY) return PA_VOLUME_MUTED; - return (pa_volume_t) lrint(ceil((dB/USER_DECIBEL_RANGE+1.0)*PA_VOLUME_NORM)); + return pa_sw_volume_from_linear(dB_to_linear(dB)); } double pa_sw_volume_to_dB(pa_volume_t v) { - if (v == PA_VOLUME_MUTED) + + if (v <= PA_VOLUME_MUTED) return PA_DECIBEL_MININFTY; - return ((double) v/PA_VOLUME_NORM-1)*USER_DECIBEL_RANGE; + return linear_to_dB(pa_sw_volume_to_linear(v)); } pa_volume_t pa_sw_volume_from_linear(double v) { @@ -141,18 +198,28 @@ pa_volume_t pa_sw_volume_from_linear(double v) { if (v <= 0.0) return PA_VOLUME_MUTED; - if (v > .999 && v < 1.001) - return PA_VOLUME_NORM; + /* + * We use a cubic mapping here, as suggested and discussed here: + * + * http://www.robotplanet.dk/audio/audio_gui_design/ + * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 + */ - return pa_sw_volume_from_dB(20.0*log10(v)); + return (pa_volume_t) (cbrt(v) * PA_VOLUME_NORM); } double pa_sw_volume_to_linear(pa_volume_t v) { + double f; - if (v == PA_VOLUME_MUTED) + if (v <= PA_VOLUME_MUTED) return 0.0; - return pow(10.0, pa_sw_volume_to_dB(v)/20.0); + if (v == PA_VOLUME_NORM) + return 1.0; + + f = ((double) v / PA_VOLUME_NORM); + + return f*f*f; } char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { @@ -225,7 +292,7 @@ char *pa_sw_cvolume_snprint_dB(char *s, size_t l, const pa_cvolume *c) { l -= pa_snprintf(e, l, "%s%u: %0.2f dB", first ? "" : " ", channel, - isinf(f) < 0 || f <= -USER_DECIBEL_RANGE ? -INFINITY : f); + isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); e = strchr(e, 0); first = FALSE; @@ -249,7 +316,7 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v) { f = pa_sw_volume_to_dB(v); pa_snprintf(s, l, "%0.2f dB", - isinf(f) < 0 || f <= -USER_DECIBEL_RANGE ? -INFINITY : f); + isinf(f) < 0 || f <= PA_DECIBEL_MININFTY ? -INFINITY : f); return s; } @@ -572,11 +639,29 @@ pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) { pa_return_val_if_fail(pa_cvolume_valid(v), NULL); pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + t = pa_cvolume_max(v); + + if (t <= PA_VOLUME_MUTED) + return pa_cvolume_set(v, v->channels, max); + for (c = 0; c < v->channels; c++) - if (v->values[c] > t) - t = v->values[c]; + v->values[c] = (pa_volume_t) (((uint64_t) v->values[c] * (uint64_t) max) / (uint64_t) t); + + return v; +} + +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask) { + unsigned c; + pa_volume_t t = 0; + + pa_assert(v); + + pa_return_val_if_fail(pa_cvolume_valid(v), NULL); + pa_return_val_if_fail(max != (pa_volume_t) -1, NULL); + + t = pa_cvolume_max_mask(v, cm, mask); - if (t <= 0) + if (t <= PA_VOLUME_MUTED) return pa_cvolume_set(v, v->channels, max); for (c = 0; c < v->channels; c++) diff --git a/src/pulse/volume.h b/src/pulse/volume.h index ddedca7d..c07fd99a 100644 --- a/src/pulse/volume.h +++ b/src/pulse/volume.h @@ -178,9 +178,23 @@ char *pa_sw_volume_snprint_dB(char *s, size_t l, pa_volume_t v); /** Return the average volume of all channels */ pa_volume_t pa_cvolume_avg(const pa_cvolume *a) PA_GCC_PURE; +/** Return the average volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_avg(). If no + * channel is selected the returned value will be + * PA_VOLUME_MUTED. \since 0.9.16 */ +pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + /** Return the maximum volume of all channels. \since 0.9.12 */ pa_volume_t pa_cvolume_max(const pa_cvolume *a) PA_GCC_PURE; +/** Return the maximum volume of all channels that are included in the + * specified channel map with the specified channel position mask. If + * cm is NULL this call is identical to pa_cvolume_max(). If no + * channel is selected the returned value will be PA_VOLUME_MUTED. + * \since 0.9.16 */ +pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE; + /** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */ int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE; @@ -212,10 +226,10 @@ pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; * *dest. This is only valid for software volumes! \since 0.9.13 */ pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b); -/** Convert a decibel value to a volume. This is only valid for software volumes! */ +/** Convert a decibel value to a volume (amplitude, not power). This is only valid for software volumes! */ pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST; -/** Convert a volume to a decibel value. This is only valid for software volumes! */ +/** Convert a volume to a decibel value (amplitude, not power). This is only valid for software volumes! */ double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST; /** Convert a linear factor to a volume. This is only valid for software volumes! */ @@ -227,7 +241,7 @@ double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST; #ifdef INFINITY #define PA_DECIBEL_MININFTY ((double) -INFINITY) #else -/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). */ +/** This floor value is used as minus infinity when using pa_volume_{to,from}_dB(). */ #define PA_DECIBEL_MININFTY ((double) -200.0) #endif @@ -283,6 +297,12 @@ pa_cvolume* pa_cvolume_set_fade(pa_cvolume *v, const pa_channel_map *map, float * volumes are kept. \since 0.9.15 */ pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max); +/** Scale the passed pa_cvolume structure so that the maximum volume + * of all channels selected via cm/mask equals max. This also modifies + * the volume of those channels that are unmasked. The proportions + * between the channel volumes are kept. \since 0.9.16 */ +pa_cvolume* pa_cvolume_scale_mask(pa_cvolume *v, pa_volume_t max, pa_channel_map *cm, pa_channel_position_mask_t mask); + /** Set the passed volume to all channels at the specified channel * position. Will return the updated volume struct, or NULL if there * is no channel at the position specified. You can check if a channel @@ -294,7 +314,7 @@ pa_cvolume* pa_cvolume_set_position(pa_cvolume *cv, const pa_channel_map *map, p * position. Will return 0 if there is no channel at the position * specified. You can check if a channel map includes a specific * position by calling pa_channel_map_has_position(). \since 0.9.16 */ -pa_volume_t pa_cvolume_get_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t); +pa_volume_t pa_cvolume_get_position(pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t) PA_GCC_PURE; PA_C_DECL_END diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c index a784f583..54514e7f 100644 --- a/src/pulsecore/cli.c +++ b/src/pulsecore/cli.c @@ -59,6 +59,8 @@ struct pa_cli { pa_bool_t fail, kill_requested; int defer_kill; + + char *last_line; }; static void line_callback(pa_ioline *line, const char *s, void *userdata); @@ -101,6 +103,8 @@ pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) { c->fail = c->kill_requested = FALSE; c->defer_kill = 0; + c->last_line = NULL; + return c; } @@ -110,6 +114,7 @@ void pa_cli_free(pa_cli *c) { pa_ioline_close(c->line); pa_ioline_unref(c->line); pa_client_free(c->client); + pa_xfree(c->last_line); pa_xfree(c); } @@ -144,6 +149,14 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { return; } + /* Magic command, like they had in AT Hayes Modems! Those were the good days! */ + if (pa_streq(s, "/")) + s = c->last_line; + else if (s[0]) { + pa_xfree(c->last_line); + c->last_line = pa_xstrdup(s); + } + pa_assert_se(buf = pa_strbuf_new()); c->defer_kill++; pa_cli_command_execute_line(c->core, s, buf, &c->fail); diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c index 34d60a8f..086f5fcb 100644 --- a/src/pulsecore/core-scache.c +++ b/src/pulsecore/core-scache.c @@ -219,11 +219,14 @@ int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint3 pa_assert(name); pa_assert(filename); - if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0) - return -1; - p = pa_proplist_new(); pa_proplist_sets(p, PA_PROP_MEDIA_FILENAME, filename); + + if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk, p) < 0) { + pa_proplist_free(p); + return -1; + } + r = pa_scache_add_item(c, name, &ss, &map, &chunk, p, idx); pa_memblock_unref(chunk.memblock); pa_proplist_free(p); @@ -311,11 +314,14 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE))) return -1; + merged = pa_proplist_new(); + pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name); + if (e->lazy && !e->memchunk.memblock) { pa_channel_map old_channel_map = e->channel_map; - if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk) < 0) - return -1; + if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk, merged) < 0) + goto fail; pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); @@ -328,7 +334,7 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t } if (!e->memchunk.memblock) - return -1; + goto fail; pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); @@ -344,17 +350,13 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t else pass_volume = FALSE; - merged = pa_proplist_new(); - pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name); pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist); if (p) pa_proplist_update(merged, PA_UPDATE_REPLACE, p); - if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) { - pa_proplist_free(merged); - return -1; - } + if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) + goto fail; pa_proplist_free(merged); @@ -362,6 +364,10 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t time(&e->last_used_time); return 0; + +fail: + pa_proplist_free(merged); + return -1; } int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, pa_proplist *p, uint32_t *sink_input_idx) { diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c index 4658eb52..b747cd84 100644 --- a/src/pulsecore/core-util.c +++ b/src/pulsecore/core-util.c @@ -2235,7 +2235,7 @@ int pa_close_all(int except_fd, ...) { int pa_close_allv(const int except_fds[]) { struct rlimit rl; - int fd; + int maxfd, fd; int saved_errno; #ifdef __linux__ @@ -2302,10 +2302,12 @@ int pa_close_allv(const int except_fds[]) { #endif - if (getrlimit(RLIMIT_NOFILE, &rl) < 0) - return -1; + if (getrlimit(RLIMIT_NOFILE, &rl) >= 0) + maxfd = (int) rl.rlim_max; + else + maxfd = sysconf(_SC_OPEN_MAX); - for (fd = 3; fd < (int) rl.rlim_max; fd++) { + for (fd = 3; fd < maxfd; fd++) { int i; pa_bool_t found; @@ -2709,3 +2711,24 @@ char *pa_realpath(const char *path) { return t; } + +void pa_disable_sigpipe(void) { + +#ifdef SIGPIPE + struct sigaction sa; + + pa_zero(sa); + + if (sigaction(SIGPIPE, NULL, &sa) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } + + sa.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } +#endif +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h index 91a4c55a..d073b750 100644 --- a/src/pulsecore/core-util.h +++ b/src/pulsecore/core-util.h @@ -227,4 +227,6 @@ char *pa_unescape(char *p); char *pa_realpath(const char *path); +void pa_disable_sigpipe(void); + #endif diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c index 73c67a8b..c7d734d9 100644 --- a/src/pulsecore/modargs.c +++ b/src/pulsecore/modargs.c @@ -84,8 +84,11 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { KEY, VALUE_START, VALUE_SIMPLE, + VALUE_SIMPLE_ESCAPED, VALUE_DOUBLE_QUOTES, - VALUE_TICKS + VALUE_DOUBLE_QUOTES_ESCAPED, + VALUE_TICKS, + VALUE_TICKS_ESCAPED } state; const char *p, *key = NULL, *value = NULL; @@ -131,9 +134,16 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { value = p+1; value_len = 0; } else if (isspace(*p)) { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_xstrdup(""), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value = p; + value_len = 1; } else { state = VALUE_SIMPLE; value = p; @@ -143,30 +153,63 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { case VALUE_SIMPLE: if (isspace(*p)) { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_SIMPLE_ESCAPED; + value_len++; } else value_len++; break; + case VALUE_SIMPLE_ESCAPED: + state = VALUE_SIMPLE; + value_len++; + break; + case VALUE_DOUBLE_QUOTES: if (*p == '"') { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_DOUBLE_QUOTES_ESCAPED; + value_len++; } else value_len++; break; + case VALUE_DOUBLE_QUOTES_ESCAPED: + state = VALUE_DOUBLE_QUOTES; + value_len++; + break; + case VALUE_TICKS: if (*p == '\'') { - if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + if (add_key_value(map, + pa_xstrndup(key, key_len), + pa_unescape(pa_xstrndup(value, value_len)), + valid_keys) < 0) goto fail; state = WHITESPACE; + } else if (*p == '\\') { + state = VALUE_TICKS_ESCAPED; + value_len++; } else value_len++; break; + + case VALUE_TICKS_ESCAPED: + state = VALUE_TICKS; + value_len++; + break; } } @@ -352,3 +395,23 @@ int pa_modargs_get_sample_spec_and_channel_map( return 0; } + +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m) { + const char *v; + pa_proplist *n; + + pa_assert(ma); + pa_assert(name); + pa_assert(p); + + if (!(v = pa_modargs_get_value(ma, name, NULL))) + return 0; + + if (!(n = pa_proplist_from_string(v))) + return -1; + + pa_proplist_update(p, m, n); + pa_proplist_free(n); + + return 0; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h index 809fb27e..b3125b10 100644 --- a/src/pulsecore/modargs.h +++ b/src/pulsecore/modargs.h @@ -58,4 +58,6 @@ structure if no channel_map is found, using pa_channel_map_init_auto() */ int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *ss, pa_channel_map *map, pa_channel_map_def_t def); +int pa_modargs_get_proplist(pa_modargs *ma, const char *name, pa_proplist *p, pa_update_mode_t m); + #endif diff --git a/src/pulsecore/sndfile-util.c b/src/pulsecore/sndfile-util.c new file mode 100644 index 00000000..032aefca --- /dev/null +++ b/src/pulsecore/sndfile-util.c @@ -0,0 +1,462 @@ +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* Shared between pacat/parec/paplay and the server */ + +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "sndfile-util.h" + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) { + SF_INFO sfi; + + pa_assert(sf); + pa_assert(ss); + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + switch (sfi.format & SF_FORMAT_SUBMASK) { + + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + ss->format = PA_SAMPLE_S16NE; + break; + + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + ss->format = PA_SAMPLE_S32NE; + break; + + case SF_FORMAT_ULAW: + ss->format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + ss->format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + break; + } + + ss->rate = (uint32_t) sfi.samplerate; + ss->channels = (uint8_t) sfi.channels; + + if (!pa_sample_spec_valid(ss)) + return -1; + + return 0; +} + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss) { + pa_assert(sfi); + pa_assert(ss); + + sfi->samplerate = (int) ss->rate; + sfi->channels = (int) ss->channels; + + if (pa_sample_format_is_le(ss->format) > 0) + sfi->format = SF_ENDIAN_LITTLE; + else if (pa_sample_format_is_be(ss->format) > 0) + sfi->format = SF_ENDIAN_BIG; + + switch (ss->format) { + + case PA_SAMPLE_U8: + ss->format = PA_SAMPLE_S16NE; + sfi->format = SF_FORMAT_PCM_U8; + break; + + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + ss->format = PA_SAMPLE_S16NE; + sfi->format |= SF_FORMAT_PCM_16; + break; + + case PA_SAMPLE_S24LE: + case PA_SAMPLE_S24BE: + case PA_SAMPLE_S24_32LE: + case PA_SAMPLE_S24_32BE: + ss->format = PA_SAMPLE_S32NE; + sfi->format |= SF_FORMAT_PCM_24; + break; + + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32RE: + ss->format = PA_SAMPLE_S32NE; + sfi->format |= SF_FORMAT_PCM_32; + break; + + case PA_SAMPLE_ULAW: + sfi->format = SF_FORMAT_ULAW; + break; + + case PA_SAMPLE_ALAW: + sfi->format = SF_FORMAT_ALAW; + break; + + case PA_SAMPLE_FLOAT32LE: + case PA_SAMPLE_FLOAT32BE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + sfi->format |= SF_FORMAT_FLOAT; + break; + } + + + if (!pa_sample_spec_valid(ss)) + return -1; + + return 0; +} + +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm) { + + static const pa_channel_position_t table[] = { + [SF_CHANNEL_MAP_MONO] = PA_CHANNEL_POSITION_MONO, + [SF_CHANNEL_MAP_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, /* libsndfile distuingishes left und front-left, which we don't */ + [SF_CHANNEL_MAP_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [SF_CHANNEL_MAP_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [SF_CHANNEL_MAP_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, + [SF_CHANNEL_MAP_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [SF_CHANNEL_MAP_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [SF_CHANNEL_MAP_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER, + [SF_CHANNEL_MAP_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT, + [SF_CHANNEL_MAP_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT, + [SF_CHANNEL_MAP_LFE] = PA_CHANNEL_POSITION_LFE, + [SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + [SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + [SF_CHANNEL_MAP_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT, + [SF_CHANNEL_MAP_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT, + [SF_CHANNEL_MAP_TOP_CENTER] = PA_CHANNEL_POSITION_TOP_CENTER, + [SF_CHANNEL_MAP_TOP_FRONT_LEFT] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + [SF_CHANNEL_MAP_TOP_FRONT_RIGHT] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + [SF_CHANNEL_MAP_TOP_FRONT_CENTER] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + [SF_CHANNEL_MAP_TOP_REAR_LEFT] = PA_CHANNEL_POSITION_TOP_REAR_LEFT, + [SF_CHANNEL_MAP_TOP_REAR_RIGHT] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT, + [SF_CHANNEL_MAP_TOP_REAR_CENTER] = PA_CHANNEL_POSITION_TOP_REAR_CENTER + }; + + SF_INFO sfi; + int *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + channels = pa_xnew(int, sfi.channels); + if (!sf_command(sf, SFC_GET_CHANNEL_MAP_INFO, + channels, sizeof(channels[0]) * sfi.channels)) { + + pa_xfree(channels); + return -1; + } + + cm->channels = (uint8_t) sfi.channels; + for (c = 0; c < cm->channels; c++) { + if (channels[c] <= SF_CHANNEL_MAP_INVALID || + (unsigned) channels[c] >= PA_ELEMENTSOF(table)) { + pa_xfree(channels); + return -1; + } + + cm->map[c] = table[channels[c]]; + } + + pa_xfree(channels); + + if (!pa_channel_map_valid(cm)) + return -1; + + return 0; +} + +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm) { + static const int table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SF_CHANNEL_MAP_MONO, + + [PA_CHANNEL_POSITION_FRONT_LEFT] = SF_CHANNEL_MAP_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SF_CHANNEL_MAP_FRONT_RIGHT, + [PA_CHANNEL_POSITION_FRONT_CENTER] = SF_CHANNEL_MAP_FRONT_CENTER, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SF_CHANNEL_MAP_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SF_CHANNEL_MAP_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SF_CHANNEL_MAP_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SF_CHANNEL_MAP_LFE, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SF_CHANNEL_MAP_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SF_CHANNEL_MAP_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = -1, + [PA_CHANNEL_POSITION_AUX1] = -1, + [PA_CHANNEL_POSITION_AUX2] = -1, + [PA_CHANNEL_POSITION_AUX3] = -1, + [PA_CHANNEL_POSITION_AUX4] = -1, + [PA_CHANNEL_POSITION_AUX5] = -1, + [PA_CHANNEL_POSITION_AUX6] = -1, + [PA_CHANNEL_POSITION_AUX7] = -1, + [PA_CHANNEL_POSITION_AUX8] = -1, + [PA_CHANNEL_POSITION_AUX9] = -1, + [PA_CHANNEL_POSITION_AUX10] = -1, + [PA_CHANNEL_POSITION_AUX11] = -1, + [PA_CHANNEL_POSITION_AUX12] = -1, + [PA_CHANNEL_POSITION_AUX13] = -1, + [PA_CHANNEL_POSITION_AUX14] = -1, + [PA_CHANNEL_POSITION_AUX15] = -1, + [PA_CHANNEL_POSITION_AUX16] = -1, + [PA_CHANNEL_POSITION_AUX17] = -1, + [PA_CHANNEL_POSITION_AUX18] = -1, + [PA_CHANNEL_POSITION_AUX19] = -1, + [PA_CHANNEL_POSITION_AUX20] = -1, + [PA_CHANNEL_POSITION_AUX21] = -1, + [PA_CHANNEL_POSITION_AUX22] = -1, + [PA_CHANNEL_POSITION_AUX23] = -1, + [PA_CHANNEL_POSITION_AUX24] = -1, + [PA_CHANNEL_POSITION_AUX25] = -1, + [PA_CHANNEL_POSITION_AUX26] = -1, + [PA_CHANNEL_POSITION_AUX27] = -1, + [PA_CHANNEL_POSITION_AUX28] = -1, + [PA_CHANNEL_POSITION_AUX29] = -1, + [PA_CHANNEL_POSITION_AUX30] = -1, + [PA_CHANNEL_POSITION_AUX31] = -1, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SF_CHANNEL_MAP_TOP_CENTER, + + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SF_CHANNEL_MAP_TOP_FRONT_LEFT, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SF_CHANNEL_MAP_TOP_FRONT_RIGHT, + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SF_CHANNEL_MAP_TOP_FRONT_CENTER , + + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SF_CHANNEL_MAP_TOP_REAR_LEFT, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SF_CHANNEL_MAP_TOP_REAR_RIGHT, + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SF_CHANNEL_MAP_TOP_REAR_CENTER, + }; + + int *channels; + unsigned c; + + pa_assert(sf); + pa_assert(cm); + + /* Suppress channel mapping for the obvious cases */ + if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_MONO) + return 0; + + if (cm->channels == 2 && + cm->map[0] == PA_CHANNEL_POSITION_FRONT_LEFT && + cm->map[1] == PA_CHANNEL_POSITION_FRONT_RIGHT) + return 0; + + channels = pa_xnew(int, cm->channels); + for (c = 0; c < cm->channels; c++) { + + if (cm->map[c] < 0 || + cm->map[c] >= PA_CHANNEL_POSITION_MAX || + table[cm->map[c]] < 0) { + pa_xfree(channels); + return -1; + } + + channels[c] = table[cm->map[c]]; + } + + if (!sf_command(sf, SFC_SET_CHANNEL_MAP_INFO, + channels, sizeof(channels[0]) * cm->channels)) { + pa_xfree(channels); + return -1; + } + + pa_xfree(channels); + return 0; +} + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p) { + + static const char* table[] = { + [SF_STR_TITLE] = PA_PROP_MEDIA_TITLE, + [SF_STR_COPYRIGHT] = PA_PROP_MEDIA_COPYRIGHT, + [SF_STR_SOFTWARE] = PA_PROP_MEDIA_SOFTWARE, + [SF_STR_ARTIST] = PA_PROP_MEDIA_ARTIST, + [SF_STR_COMMENT] = "media.comment", + [SF_STR_DATE] = "media.date" + }; + + SF_INFO sfi; + SF_FORMAT_INFO fi; + unsigned c; + + pa_assert(sf); + pa_assert(p); + + for (c = 0; c < PA_ELEMENTSOF(table); c++) { + const char *s; + char *t; + + if (!table[c]) + continue; + + if (!(s = sf_get_string(sf, c))) + continue; + + t = pa_utf8_filter(s); + pa_proplist_sets(p, table[c], t); + pa_xfree(t); + } + + pa_zero(sfi); + pa_assert_se(sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)) == 0); + + pa_zero(fi); + fi.format = sfi.format; + if (sf_command(sf, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name) { + char *t; + + t = pa_utf8_filter(fi.name); + pa_proplist_sets(p, "media.format", t); + pa_xfree(t); + } +} + +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss) { + pa_assert(ss); + + switch (ss->format) { + case PA_SAMPLE_S16NE: + return (pa_sndfile_readf_t) sf_readf_short; + + case PA_SAMPLE_S32NE: + return (pa_sndfile_readf_t) sf_readf_int; + + case PA_SAMPLE_FLOAT32NE: + return (pa_sndfile_readf_t) sf_readf_float; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + return NULL; + + default: + pa_assert_not_reached(); + } +} + +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss) { + pa_assert(ss); + + switch (ss->format) { + case PA_SAMPLE_S16NE: + return (pa_sndfile_writef_t) sf_writef_short; + + case PA_SAMPLE_S32NE: + return (pa_sndfile_writef_t) sf_writef_int; + + case PA_SAMPLE_FLOAT32NE: + return (pa_sndfile_writef_t) sf_writef_float; + + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + return NULL; + + default: + pa_assert_not_reached(); + } +} + +int pa_sndfile_format_from_string(const char *name) { + int i, count = 0; + + + if (!name[0]) + return -1; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + + /* First try to match via full type string */ + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + if (strcasecmp(name, fi.name) == 0) + return i; + } + + /* Then, try to match via the full extension */ + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + if (strcasecmp(name, fi.extension) == 0) + return i; + } + + /* Then, try to match via the start of the type string */ + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + + if (strncasecmp(name, fi.extension, strlen(name)) == 0) + return i; + } + + return -1; +} + +void pa_sndfile_dump_formats(void) { + int i, count = 0; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) == 0); + + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + pa_zero(fi); + fi.format = i; + + pa_assert_se(sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) == 0); + printf("%s\t%s\n", fi.extension, fi.name); + } +} diff --git a/src/pulsecore/sndfile-util.h b/src/pulsecore/sndfile-util.h new file mode 100644 index 00000000..74021da1 --- /dev/null +++ b/src/pulsecore/sndfile-util.h @@ -0,0 +1,52 @@ +#ifndef foopulsecoresndfileutilhfoo +#define foopulsecoresndfileutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2009 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sndfile.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/proplist.h> + +int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss); +int pa_sndfile_read_channel_map(SNDFILE *sf, pa_channel_map *cm); + +int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss); +int pa_sndfile_write_channel_map(SNDFILE *sf, pa_channel_map *cm); + +void pa_sndfile_init_proplist(SNDFILE *sf, pa_proplist *p); + +typedef sf_count_t (*pa_sndfile_readf_t)(SNDFILE *sndfile, void *ptr, sf_count_t frames); +typedef sf_count_t (*pa_sndfile_writef_t)(SNDFILE *sndfile, const void *ptr, sf_count_t frames); + +/* Returns NULL if sf_read_raw() shall be used */ +pa_sndfile_readf_t pa_sndfile_readf_function(const pa_sample_spec *ss); + +/* Returns NULL if sf_write_raw() shall be used */ +pa_sndfile_writef_t pa_sndfile_writef_function(const pa_sample_spec *ss); + +int pa_sndfile_format_from_string(const char *extension); + +void pa_sndfile_dump_formats(void); + +#endif diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c index 3453637f..502e5c69 100644 --- a/src/pulsecore/sound-file-stream.c +++ b/src/pulsecore/sound-file-stream.c @@ -41,6 +41,7 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/core-util.h> #include <pulsecore/sample-util.h> +#include <pulsecore/sndfile-util.h> #include "sound-file-stream.h" @@ -234,10 +235,11 @@ int pa_play_file( const pa_cvolume *volume) { file_stream *u = NULL; - SF_INFO sfinfo; pa_sample_spec ss; + pa_channel_map cm; pa_sink_input_new_data data; int fd; + SF_INFO sfi; pa_assert(sink); pa_assert(fname); @@ -251,8 +253,6 @@ int pa_play_file( u->readf_function = NULL; u->memblockq = NULL; - memset(&sfinfo, 0, sizeof(sfinfo)); - if ((fd = open(fname, O_RDONLY #ifdef O_NOCTTY |O_NOCTTY @@ -281,50 +281,36 @@ int pa_play_file( pa_log_debug("POSIX_FADV_WILLNEED succeeded."); #endif - if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_zero(sfi); + if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) { pa_log("Failed to open file %s", fname); - pa_close(fd); goto fail; } - switch (sfinfo.format & 0xFF) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss.format = PA_SAMPLE_S16NE; - u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - ss.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss.format = PA_SAMPLE_ALAW; - break; + fd = -1; - case SF_FORMAT_FLOAT: - default: - ss.format = PA_SAMPLE_FLOAT32NE; - u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float; - break; + if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) { + pa_log("Failed to determine file sample format."); + goto fail; } - ss.rate = (uint32_t) sfinfo.samplerate; - ss.channels = (uint8_t) sfinfo.channels; - - if (!pa_sample_spec_valid(&ss)) { - pa_log("Unsupported sample format in file %s", fname); - goto fail; + if (pa_sndfile_read_channel_map(u->sndfile, &cm) < 0) { + if (ss.channels > 2) + pa_log_info("Failed to determine file channel map, synthesizing one."); + pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); } + u->readf_function = pa_sndfile_readf_function(&ss); + pa_sink_input_new_data_init(&data); data.sink = sink; data.driver = __FILE__; pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &cm); pa_sink_input_new_data_set_volume(&data, volume); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname)); pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname); + pa_sndfile_init_proplist(u->sndfile, data.proplist); pa_sink_input_new(&u->sink_input, sink->core, &data, 0); pa_sink_input_new_data_done(&data); @@ -352,5 +338,8 @@ fail: if (u) file_stream_unref(u); + if (fd >= 0) + pa_close(fd); + return -1; } diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c index db75ae08..2d9b76ad 100644 --- a/src/pulsecore/sound-file.c +++ b/src/pulsecore/sound-file.c @@ -35,19 +35,21 @@ #include <pulsecore/macro.h> #include <pulsecore/core-error.h> #include <pulsecore/core-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sndfile-util.h> #include "sound-file.h" -#include "core-scache.h" int pa_sound_file_load( pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, - pa_memchunk *chunk) { + pa_memchunk *chunk, + pa_proplist *p) { SNDFILE *sf = NULL; - SF_INFO sfinfo; + SF_INFO sfi; int ret = -1; size_t l; sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL; @@ -59,7 +61,6 @@ int pa_sound_file_load( pa_assert(chunk); pa_memchunk_reset(chunk); - memset(&sfinfo, 0, sizeof(sfinfo)); if ((fd = open(fname, O_RDONLY #ifdef O_NOCTTY @@ -78,48 +79,29 @@ int pa_sound_file_load( pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); #endif - if (!(sf = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_zero(sfi); + if (!(sf = sf_open_fd(fd, SFM_READ, &sfi, 1))) { pa_log("Failed to open file %s", fname); - pa_close(fd); goto finish; } - switch (sfinfo.format & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss->format = PA_SAMPLE_S16NE; - readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - ss->format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss->format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_FLOAT: - case SF_FORMAT_DOUBLE: - default: - ss->format = PA_SAMPLE_FLOAT32NE; - readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *_ptr, sf_count_t frames)) sf_readf_float; - break; - } - - ss->rate = (uint32_t) sfinfo.samplerate; - ss->channels = (uint8_t) sfinfo.channels; + fd = -1; - if (!pa_sample_spec_valid(ss)) { - pa_log("Unsupported sample format in file %s", fname); + if (pa_sndfile_read_sample_spec(sf, ss) < 0) { + pa_log("Failed to determine file sample format."); goto finish; } - if (map) + if ((map && pa_sndfile_read_channel_map(sf, map) < 0)) { + if (ss->channels > 2) + pa_log("Failed to determine file channel map, synthesizing one."); pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_DEFAULT); + } - if ((l = pa_frame_size(ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + if (p) + pa_sndfile_init_proplist(sf, p); + + if ((l = pa_frame_size(ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { pa_log("File too large"); goto finish; } @@ -128,9 +110,11 @@ int pa_sound_file_load( chunk->index = 0; chunk->length = l; + readf_function = pa_sndfile_readf_function(ss); + ptr = pa_memblock_acquire(chunk->memblock); - if ((readf_function && readf_function(sf, ptr, sfinfo.frames) != sfinfo.frames) || + if ((readf_function && readf_function(sf, ptr, sfi.frames) != sfi.frames) || (!readf_function && sf_read_raw(sf, ptr, (sf_count_t) l) != (sf_count_t) l)) { pa_log("Premature file end"); goto finish; @@ -149,55 +133,35 @@ finish: if (ret != 0 && chunk->memblock) pa_memblock_unref(chunk->memblock); + if (fd >= 0) + pa_close(fd); + return ret; } int pa_sound_file_too_big_to_cache(const char *fname) { SNDFILE*sf = NULL; - SF_INFO sfinfo; + SF_INFO sfi; pa_sample_spec ss; pa_assert(fname); - if (!(sf = sf_open(fname, SFM_READ, &sfinfo))) { + pa_zero(sfi); + if (!(sf = sf_open(fname, SFM_READ, &sfi))) { pa_log("Failed to open file %s", fname); return -1; } - sf_close(sf); - - switch (sfinfo.format & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - ss.format = PA_SAMPLE_S16NE; - break; - - case SF_FORMAT_ULAW: - ss.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - ss.format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_DOUBLE: - case SF_FORMAT_FLOAT: - default: - ss.format = PA_SAMPLE_FLOAT32NE; - break; - } - - ss.rate = (uint32_t) sfinfo.samplerate; - ss.channels = (uint8_t) sfinfo.channels; - - if (!pa_sample_spec_valid(&ss)) { - pa_log("Unsupported sample format in file %s", fname); + if (pa_sndfile_read_sample_spec(sf, &ss) < 0) { + pa_log("Failed to determine file sample format."); + sf_close(sf); return -1; } - if ((pa_frame_size(&ss) * (size_t) sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + sf_close(sf); + + if ((pa_frame_size(&ss) * (size_t) sfi.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { pa_log("File too large: %s", fname); return 1; } diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h index 34e02616..4dc0c7e1 100644 --- a/src/pulsecore/sound-file.h +++ b/src/pulsecore/sound-file.h @@ -26,7 +26,7 @@ #include <pulse/channelmap.h> #include <pulsecore/memchunk.h> -int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk); +int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk, pa_proplist *p); int pa_sound_file_too_big_to_cache(const char *fname); diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c index 3e723561..27a0d3fe 100644 --- a/src/tests/proplist-test.c +++ b/src/tests/proplist-test.c @@ -27,11 +27,14 @@ #include <pulse/xmalloc.h> #include <pulsecore/macro.h> #include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> int main(int argc, char*argv[]) { + pa_modargs *ma; pa_proplist *a, *b, *c, *d; char *s, *t, *u, *v; const char *text; + const char *x[] = { "foo", NULL }; a = pa_proplist_new(); pa_assert_se(pa_proplist_sets(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0); @@ -78,5 +81,16 @@ int main(int argc, char*argv[]) { printf("%s\n", v); pa_xfree(v); + pa_assert_se(ma = pa_modargs_new("foo='foobar=waldo foo2=\"lj\\\\\"dhflh\" foo3=\\'kjlskj\\\\\\'\\''", x)); + pa_assert_se(a = pa_proplist_new()); + + pa_assert_se(pa_modargs_get_proplist(ma, "foo", a, PA_UPDATE_REPLACE) >= 0); + + printf("%s\n", v = pa_proplist_to_string(a)); + pa_xfree(v); + + pa_proplist_free(a); + pa_modargs_free(ma); + return 0; } diff --git a/src/tests/volume-ui.py b/src/tests/volume-ui.py index 6dc1c47d..7909b801 100644 --- a/src/tests/volume-ui.py +++ b/src/tests/volume-ui.py @@ -111,6 +111,10 @@ class CVolume(Structure): _set_fade.restype = c_void_p _set_fade.argtypes = [c_void_p, c_void_p, c_float] + _to_dB = libpulse.pa_sw_volume_to_dB + _to_dB.restype = c_double + _to_dB.argytpes = [c_uint32] + def snprint(this): s = create_string_buffer(320) r = this._snprint(s, len(s), byref(this)) @@ -138,6 +142,12 @@ class CVolume(Structure): def set_fade(this, cm, f): return this._set_fade(byref(this), byref(cm), f) + def to_dB(this, channel = None): + if channel is None: + return this._to_dB(this.max()) + + return this._to_dB(this.values[channel]) + cm = ChannelMap() if len(sys.argv) > 1: @@ -149,7 +159,7 @@ v = CVolume() v.channels = cm.channels for i in range(cm.channels): - v.values[i] = 65536/2 + v.values[i] = 65536 title = cm.to_pretty_name() if title is None: @@ -163,6 +173,7 @@ vbox = gtk.VBox(spacing=6) channel_labels = {} channel_scales = {} +channel_dB_labels = {} def update_volume(update_channels = True, update_fade = True, update_balance = True, update_scale = True): if update_channels: @@ -178,6 +189,11 @@ def update_volume(update_channels = True, update_fade = True, update_balance = T if update_fade: fade_scale.set_value(v.get_fade(cm)) + for i in range(cm.channels): + channel_dB_labels[i].set_label("%0.2f dB" % v.to_dB(i)) + + value_dB_label.set_label("%0.2f dB" % v.to_dB()) + def fade_value_changed(fs): v.set_fade(cm, fade_scale.get_value()) update_volume(update_fade = False) @@ -200,19 +216,26 @@ for i in range(cm.channels): vbox.pack_start(channel_labels[i], expand=False, fill=True) channel_scales[i] = gtk.HScale() - channel_scales[i].set_range(0, 65536) + channel_scales[i].set_range(0, 65536*3/2) channel_scales[i].set_digits(0) channel_scales[i].set_value_pos(gtk.POS_RIGHT) vbox.pack_start(channel_scales[i], expand=False, fill=True) + channel_dB_labels[i] = gtk.Label("-xxx dB") + channel_dB_labels[i].set_alignment(1, 1) + vbox.pack_start(channel_dB_labels[i], expand=False, fill=True) + value_label = gtk.Label("Value") value_label.set_alignment(0, .5) vbox.pack_start(value_label, expand=False, fill=True) value_scale = gtk.HScale() -value_scale.set_range(0, 65536) +value_scale.set_range(0, 65536*3/2) value_scale.set_value_pos(gtk.POS_RIGHT) value_scale.set_digits(0) vbox.pack_start(value_scale, expand=False, fill=True) +value_dB_label = gtk.Label("-xxx dB") +value_dB_label.set_alignment(1, 1) +vbox.pack_start(value_dB_label, expand=False, fill=True) balance_label = gtk.Label("Balance") balance_label.set_alignment(0, .5) diff --git a/src/utils/pabrowse.c b/src/utils/pabrowse.c index 288d44a9..a6487b88 100644 --- a/src/utils/pabrowse.c +++ b/src/utils/pabrowse.c @@ -30,6 +30,8 @@ #include <pulse/pulseaudio.h> #include <pulse/browser.h> +#include <pulsecore/core-util.h> + static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { fprintf(stderr, "Got signal, exiting\n"); m->quit(m, 0); @@ -127,7 +129,7 @@ int main(int argc, char *argv[]) { assert(r == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); pa_signal_new(SIGTERM, exit_signal_callback, NULL); - signal(SIGPIPE, SIG_IGN); + pa_disable_sigpipe(); if (!(browser = pa_browser_new_full(pa_mainloop_get_api(mainloop), PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, &s))) { fprintf(stderr, "pa_browse_new_full(): %s\n", s); diff --git a/src/utils/pacat.c b/src/utils/pacat.c index 15e842f3..0b6df3d8 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -35,9 +35,16 @@ #include <fcntl.h> #include <locale.h> +#include <sndfile.h> + #include <pulse/i18n.h> #include <pulse/pulseaudio.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sndfile-util.h> + #define TIME_EVENT_USEC 50000 #define CLEAR_LINE "\x1B[K" @@ -53,35 +60,92 @@ static size_t buffer_length = 0, buffer_index = 0; static pa_io_event* stdio_event = NULL; -static char *stream_name = NULL, *client_name = NULL, *device = NULL; +static pa_proplist *proplist = NULL; +static char *device = NULL; + +static SNDFILE* sndfile = NULL; -static int verbose = 0; +static pa_bool_t verbose = FALSE; static pa_volume_t volume = PA_VOLUME_NORM; -static int volume_is_set = 0; +static pa_bool_t 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 pa_channel_map channel_map; -static int channel_map_set = 0; +static pa_bool_t 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; static pa_stream_flags_t flags = 0; -static size_t latency = 0, process_time=0; +static size_t latency = 0, process_time = 0; + +static pa_bool_t raw = TRUE; +static int file_format = -1; /* A shortcut for terminating the application */ static void quit(int ret) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->quit(mainloop_api, ret); } +/* Connection draining complete */ +static void context_drain_complete(pa_context*c, void *userdata) { + pa_context_disconnect(c); +} + +/* Stream draining complete */ +static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + + if (!success) { + pa_log(_("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + } + + if (verbose) + pa_log(_("Playback stream drained.\n")); + + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = NULL; + + if (!pa_context_drain(context, context_drain_complete, NULL)) + pa_context_disconnect(context); + else { + if (verbose) + pa_log(_("Draining connection to server.\n")); + } +} + +/* Start draining */ +static void start_drain(void) { + + if (stream) { + pa_operation *o; + + pa_stream_set_write_callback(stream, NULL, NULL); + + if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { + pa_log(_("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_operation_unref(o); + } else + quit(0); +} + /* Write some data to the stream */ static void do_stream_write(size_t length) { size_t l; - assert(length); + pa_assert(length); if (!buffer || !buffer_length) return; @@ -91,7 +155,7 @@ static void do_stream_write(size_t length) { l = buffer_length; if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) { - fprintf(stderr, _("pa_stream_write() failed: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_stream_write() failed: %s\n"), pa_strerror(pa_context_errno(context))); quit(1); return; } @@ -108,53 +172,121 @@ static void do_stream_write(size_t length) { /* This is called whenever new data may be written to the stream */ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { - assert(s); - assert(length > 0); + pa_assert(s); + pa_assert(length > 0); - if (stdio_event) - mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); + if (raw) { + pa_assert(!sndfile); - if (!buffer) - return; + if (stdio_event) + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); + + if (!buffer) + return; + + do_stream_write(length); + + } else { + sf_count_t bytes; + void *data; + + pa_assert(sndfile); + + data = pa_xmalloc(length); + + if (readf_function) { + size_t k = pa_frame_size(&sample_spec); - do_stream_write(length); + if ((bytes = readf_function(sndfile, data, (sf_count_t) (length/k))) > 0) + bytes *= (sf_count_t) k; + + } else + bytes = sf_read_raw(sndfile, data, (sf_count_t) length); + + if (bytes > 0) + pa_stream_write(s, data, (size_t) bytes, pa_xfree, 0, PA_SEEK_RELATIVE); + else + pa_xfree(data); + + if (bytes < (sf_count_t) length) + start_drain(); + } } /* This is called whenever new data may is available */ static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { - const void *data; - assert(s); - assert(length > 0); - if (stdio_event) - mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); + pa_assert(s); + pa_assert(length > 0); - if (pa_stream_peek(s, &data, &length) < 0) { - fprintf(stderr, _("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - return; - } + if (raw) { + pa_assert(!sndfile); - assert(data); - assert(length > 0); + if (stdio_event) + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); + + + while (pa_stream_readable_size(s) > 0) { + const void *data; + + if (pa_stream_peek(s, &data, &length) < 0) { + pa_log(_("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_assert(data); + pa_assert(length > 0); + + if (buffer) { + buffer = pa_xrealloc(buffer, buffer_length + length); + memcpy((uint8_t*) buffer + buffer_length, data, length); + buffer_length += length; + } else { + buffer = pa_xmalloc(length); + memcpy(buffer, data, length); + buffer_length = length; + buffer_index = 0; + } + pa_stream_drop(s); + } - if (buffer) { - buffer = pa_xrealloc(buffer, buffer_length + length); - memcpy((uint8_t*) buffer + buffer_length, data, length); - buffer_length += length; } else { - buffer = pa_xmalloc(length); - memcpy(buffer, data, length); - buffer_length = length; - buffer_index = 0; - } + pa_assert(sndfile); + + while (pa_stream_readable_size(s) > 0) { + sf_count_t bytes; + const void *data; + + if (pa_stream_peek(s, &data, &length) < 0) { + pa_log(_("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_assert(data); + pa_assert(length > 0); + + if (writef_function) { + size_t k = pa_frame_size(&sample_spec); - pa_stream_drop(s); + if ((bytes = writef_function(sndfile, data, (sf_count_t) (length/k))) > 0) + bytes *= (sf_count_t) k; + + } else + bytes = sf_write_raw(sndfile, data, (sf_count_t) length); + + if (bytes < (sf_count_t) length) + quit(1); + + pa_stream_drop(s); + } + } } /* This routine is called whenever the stream state changes */ static void stream_state_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); switch (pa_stream_get_state(s)) { case PA_STREAM_CREATING: @@ -162,29 +294,30 @@ static void stream_state_callback(pa_stream *s, void *userdata) { break; case PA_STREAM_READY: + if (verbose) { const pa_buffer_attr *a; char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; - fprintf(stderr, _("Stream successfully created.\n")); + pa_log(_("Stream successfully created.\n")); if (!(a = pa_stream_get_buffer_attr(s))) - fprintf(stderr, _("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("pa_stream_get_buffer_attr() failed: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); else { if (mode == PLAYBACK) - fprintf(stderr, _("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq); + pa_log(_("Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n"), a->maxlength, a->tlength, a->prebuf, a->minreq); else { - assert(mode == RECORD); - fprintf(stderr, _("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize); + pa_assert(mode == RECORD); + pa_log(_("Buffer metrics: maxlength=%u, fragsize=%u\n"), a->maxlength, a->fragsize); } } - fprintf(stderr, _("Using sample spec '%s', channel map '%s'.\n"), + pa_log(_("Using sample spec '%s', channel map '%s'.\n"), 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))); - fprintf(stderr, _("Connected to device %s (%u, %ssuspended).\n"), + pa_log(_("Connected to device %s (%u, %ssuspended).\n"), pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not "); @@ -194,72 +327,72 @@ static void stream_state_callback(pa_stream *s, void *userdata) { case PA_STREAM_FAILED: default: - fprintf(stderr, _("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("Stream error: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); quit(1); } } static void stream_suspended_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) { if (pa_stream_is_suspended(s)) - fprintf(stderr, _("Stream device suspended.%s \n"), CLEAR_LINE); + pa_log(_("Stream device suspended.%s \n"), CLEAR_LINE); else - fprintf(stderr, _("Stream device resumed.%s \n"), CLEAR_LINE); + pa_log(_("Stream device resumed.%s \n"), CLEAR_LINE); } } static void stream_underflow_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream underrun.%s \n"), CLEAR_LINE); + pa_log(_("Stream underrun.%s \n"), CLEAR_LINE); } static void stream_overflow_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream overrun.%s \n"), CLEAR_LINE); + pa_log(_("Stream overrun.%s \n"), CLEAR_LINE); } static void stream_started_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream started.%s \n"), CLEAR_LINE); + pa_log(_("Stream started.%s \n"), CLEAR_LINE); } static void stream_moved_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - 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); + pa_log(_("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); } static void stream_buffer_attr_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); if (verbose) - fprintf(stderr, _("Stream buffer attributes changed.%s \n"), CLEAR_LINE); + pa_log(_("Stream buffer attributes changed.%s \n"), CLEAR_LINE); } static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *pl, void *userdata) { char *t; - assert(s); - assert(name); - assert(pl); + pa_assert(s); + pa_assert(name); + pa_assert(pl); t = pa_proplist_to_string_sep(pl, ", "); - fprintf(stderr, "Got event '%s', properties '%s'\n", name, t); + pa_log("Got event '%s', properties '%s'\n", name, t); pa_xfree(t); } /* This is called whenever the context status changes */ static void context_state_callback(pa_context *c, void *userdata) { - assert(c); + pa_assert(c); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: @@ -271,14 +404,14 @@ static void context_state_callback(pa_context *c, void *userdata) { int r; pa_buffer_attr buffer_attr; - assert(c); - assert(!stream); + pa_assert(c); + pa_assert(!stream); if (verbose) - fprintf(stderr, _("Connection established.%s \n"), CLEAR_LINE); + pa_log(_("Connection established.%s \n"), CLEAR_LINE); - if (!(stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL))) { - fprintf(stderr, _("pa_stream_new() failed: %s\n"), pa_strerror(pa_context_errno(c))); + if (!(stream = pa_stream_new_with_proplist(c, NULL, &sample_spec, &channel_map, proplist))) { + pa_log(_("pa_stream_new() failed: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } @@ -306,13 +439,13 @@ static void context_state_callback(pa_context *c, void *userdata) { if (mode == PLAYBACK) { pa_cvolume cv; 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) { - fprintf(stderr, _("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("pa_stream_connect_playback() failed: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } } else { if ((r = pa_stream_connect_record(stream, device, latency > 0 ? &buffer_attr : NULL, flags)) < 0) { - fprintf(stderr, _("pa_stream_connect_record() failed: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("pa_stream_connect_record() failed: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } } @@ -326,7 +459,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: - fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); goto fail; } @@ -337,42 +470,14 @@ fail: } -/* Connection draining complete */ -static void context_drain_complete(pa_context*c, void *userdata) { - pa_context_disconnect(c); -} - -/* Stream draining complete */ -static void stream_drain_complete(pa_stream*s, int success, void *userdata) { - - if (!success) { - fprintf(stderr, _("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - } - - if (verbose) - fprintf(stderr, _("Playback stream drained.\n")); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = NULL; - - if (!pa_context_drain(context, context_drain_complete, NULL)) - pa_context_disconnect(context); - else { - if (verbose) - fprintf(stderr, _("Draining connection to server.\n")); - } -} - /* New data on STDIN **/ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { size_t l, w = 0; ssize_t r; - assert(a == mainloop_api); - assert(e); - assert(stdio_event == e); + pa_assert(a == mainloop_api); + pa_assert(e); + pa_assert(stdio_event == e); if (buffer) { mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); @@ -387,23 +492,12 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even if ((r = read(fd, buffer, l)) <= 0) { if (r == 0) { if (verbose) - fprintf(stderr, _("Got EOF.\n")); - - if (stream) { - pa_operation *o; - - if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { - fprintf(stderr, _("pa_stream_drain(): %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - return; - } + pa_log(_("Got EOF.\n")); - pa_operation_unref(o); - } else - quit(0); + start_drain(); } else { - fprintf(stderr, _("read() failed: %s\n"), strerror(errno)); + pa_log(_("read() failed: %s\n"), strerror(errno)); quit(1); } @@ -423,19 +517,19 @@ static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_even static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { ssize_t r; - assert(a == mainloop_api); - assert(e); - assert(stdio_event == e); + pa_assert(a == mainloop_api); + pa_assert(e); + pa_assert(stdio_event == e); if (!buffer) { mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); return; } - assert(buffer_length); + pa_assert(buffer_length); if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) { - fprintf(stderr, _("write() failed: %s\n"), strerror(errno)); + pa_log(_("write() failed: %s\n"), strerror(errno)); quit(1); mainloop_api->io_free(stdio_event); @@ -456,7 +550,7 @@ static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_eve /* UNIX signal to quit recieved */ static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { if (verbose) - fprintf(stderr, _("Got signal, exiting.\n")); + pa_log(_("Got signal, exiting.\n")); quit(0); } @@ -465,17 +559,17 @@ static void stream_update_timing_callback(pa_stream *s, int success, void *userd pa_usec_t l, usec; int negative = 0; - assert(s); + pa_assert(s); if (!success || pa_stream_get_time(s, &usec) < 0 || pa_stream_get_latency(s, &l, &negative) < 0) { - fprintf(stderr, _("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("Failed to get latency: %s\n"), pa_strerror(pa_context_errno(context))); quit(1); return; } - fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec. \r"), + pa_log(_("Time: %0.3f sec; Latency: %0.0f usec. \r"), (float) usec / 1000000, (float) l * (negative?-1.0f:1.0f)); } @@ -495,7 +589,7 @@ static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struc if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) { pa_operation *o; if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL))) - fprintf(stderr, _("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_stream_update_timing_info() failed: %s\n"), pa_strerror(pa_context_errno(context))); else pa_operation_unref(o); } @@ -509,34 +603,38 @@ static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struc static void help(const char *argv0) { printf(_("%s [options]\n\n" - " -h, --help Show this help\n" - " --version Show version\n\n" - " -r, --record Create a connection for recording\n" - " -p, --playback Create a connection for playback\n\n" - " -v, --verbose Enable verbose operations\n\n" - " -s, --server=SERVER The name of the server to connect to\n" - " -d, --device=DEVICE The name of the sink/source to connect to\n" - " -n, --client-name=NAME How to call this client on the server\n" - " --stream-name=NAME How to call this stream on the server\n" - " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" - " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n" - " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n" - " float32be, ulaw, alaw, s32le, s32be (defaults to s16ne)\n" - " --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" - " being connected to.\n" - " --fix-rate Take the sampling rate from the sink 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" - " --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") - , - argv0); + " -h, --help Show this help\n" + " --version Show version\n\n" + " -r, --record Create a connection for recording\n" + " -p, --playback Create a connection for playback\n\n" + " -v, --verbose Enable verbose operations\n\n" + " -s, --server=SERVER The name of the server to connect to\n" + " -d, --device=DEVICE The name of the sink/source to connect to\n" + " -n, --client-name=NAME How to call this client on the server\n" + " --stream-name=NAME How to call this stream on the server\n" + " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" + " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n" + " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n" + " float32be, ulaw, alaw, s32le, s32be, s24le, s24be,\n" + " s24-32le, s24-32be (defaults to s16ne)\n" + " --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" + " being connected to.\n" + " --fix-rate Take the sampling rate from the sink 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" + " --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" + " --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") + , argv0); } enum { @@ -553,14 +651,19 @@ enum { ARG_NO_REMAP, ARG_NO_REMIX, ARG_LATENCY, - ARG_PROCESS_TIME + ARG_PROCESS_TIME, + ARG_RAW, + ARG_PROPERTY, + ARG_FILE_FORMAT, + ARG_LIST_FILE_FORMATS }; int main(int argc, char *argv[]) { pa_mainloop* m = NULL; - int ret = 1, r, c; + int ret = 1, c; char *bn, *server = NULL; pa_time_event *time_event = NULL; + const char *filename = NULL; static const struct option long_options[] = { {"record", 0, NULL, 'r'}, @@ -584,21 +687,33 @@ int main(int argc, char *argv[]) { {"no-remix", 0, NULL, ARG_NO_REMIX}, {"latency", 1, NULL, ARG_LATENCY}, {"process-time", 1, NULL, ARG_PROCESS_TIME}, + {"property", 1, NULL, ARG_PROPERTY}, + {"raw", 0, NULL, ARG_RAW}, + {"file-format", 2, NULL, ARG_FILE_FORMAT}, + {"list-file-formats", 0, NULL, ARG_LIST_FILE_FORMATS}, {NULL, 0, NULL, 0} }; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; + bn = pa_path_get_filename(argv[0]); - if (strstr(bn, "rec") || strstr(bn, "mon")) + if (strstr(bn, "play")) { + mode = PLAYBACK; + raw = FALSE; + } else if (strstr(bn, "record")) { mode = RECORD; - else if (strstr(bn, "cat") || strstr(bn, "play")) + raw = FALSE; + } else if (strstr(bn, "cat")) { mode = PLAYBACK; + raw = TRUE; + } if (strstr(bn, "rec") || strstr(bn, "mon")) { + mode = RECORD; + raw = TRUE; + } + + proplist = pa_proplist_new(); while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) { @@ -609,7 +724,12 @@ int main(int argc, char *argv[]) { goto quit; case ARG_VERSION: - printf(_("pacat %s\nCompiled with libpulse %s\nLinked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version()); + printf(_("pacat %s\n" + "Compiled with libpulse %s\n" + "Linked with libpulse %s\n"), + PACKAGE_VERSION, + pa_get_headers_version(), + pa_get_library_version()); ret = 0; goto quit; @@ -631,15 +751,36 @@ int main(int argc, char *argv[]) { server = pa_xstrdup(optarg); break; - case 'n': - pa_xfree(client_name); - client_name = pa_xstrdup(optarg); + case 'n': { + char *t; + + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t) < 0) { + + pa_log(_("Invalid client name '%s'\n"), t ? t : optarg); + pa_xfree(t); + goto quit; + } + + pa_xfree(t); break; + } + + case ARG_STREAM_NAME: { + char *t; + t = pa_locale_to_utf8(optarg); - case ARG_STREAM_NAME: - pa_xfree(stream_name); - stream_name = pa_xstrdup(optarg); + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t) < 0) { + + pa_log(_("Invalid stream name '%s'\n"), t ? t : optarg); + pa_xfree(t); + goto quit; + } + + pa_xfree(t); break; + } case 'v': verbose = 1; @@ -648,29 +789,32 @@ int main(int argc, char *argv[]) { case ARG_VOLUME: { int v = atoi(optarg); volume = v < 0 ? 0U : (pa_volume_t) v; - volume_is_set = 1; + volume_is_set = TRUE; break; } case ARG_CHANNELS: sample_spec.channels = (uint8_t) atoi(optarg); + sample_spec_set = TRUE; break; case ARG_SAMPLEFORMAT: sample_spec.format = pa_parse_sample_format(optarg); + sample_spec_set = TRUE; break; case ARG_SAMPLERATE: sample_spec.rate = (uint32_t) atoi(optarg); + sample_spec_set = TRUE; break; case ARG_CHANNELMAP: if (!pa_channel_map_parse(&channel_map, optarg)) { - fprintf(stderr, _("Invalid channel map '%s'\n"), optarg); + pa_log(_("Invalid channel map '%s'\n"), optarg); goto quit; } - channel_map_set = 1; + channel_map_set = TRUE; break; case ARG_FIX_CHANNELS: @@ -695,17 +839,54 @@ int main(int argc, char *argv[]) { case ARG_LATENCY: if (((latency = (size_t) atoi(optarg))) <= 0) { - fprintf(stderr, _("Invalid latency specification '%s'\n"), optarg); + pa_log(_("Invalid latency specification '%s'\n"), optarg); goto quit; } break; case ARG_PROCESS_TIME: if (((process_time = (size_t) atoi(optarg))) <= 0) { - fprintf(stderr, _("Invalid process time specification '%s'\n"), optarg); + pa_log(_("Invalid process time specification '%s'\n"), optarg); + goto quit; + } + break; + + case ARG_PROPERTY: { + char *t; + + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_setp(proplist, t) < 0) { + + pa_xfree(t); + pa_log(_("Invalid property '%s'\n"), optarg); goto quit; } + + pa_xfree(t); break; + } + + case ARG_RAW: + raw = TRUE; + break; + + case ARG_FILE_FORMAT: + raw = FALSE; + + if (optarg) { + if ((file_format = pa_sndfile_format_from_string(optarg)) < 0) { + pa_log(_("Unknown file format %s."), optarg); + goto quit; + } + } + + raw = FALSE; + break; + + case ARG_LIST_FILE_FORMATS: + pa_sndfile_dump_formats(); + ret = 0; + goto quit; default: goto quit; @@ -713,82 +894,168 @@ int main(int argc, char *argv[]) { } if (!pa_sample_spec_valid(&sample_spec)) { - fprintf(stderr, _("Invalid sample specification\n")); + pa_log(_("Invalid sample specification\n")); goto quit; } - if (channel_map_set && pa_channel_map_compatible(&channel_map, &sample_spec)) { - fprintf(stderr, _("Channel map doesn't match sample specification\n")); - goto quit; - } + if (optind+1 == argc) { + int fd; - if (verbose) { - char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; - pa_sample_spec_snprint(t, sizeof(t), &sample_spec); - fprintf(stderr, _("Opening a %s stream with sample specification '%s'.\n"), mode == RECORD ? _("recording") : _("playback"), t); + filename = argv[optind]; + + if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { + pa_log(_("open(): %s\n"), strerror(errno)); + goto quit; + } + + if (dup2(fd, mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO) < 0) { + pa_log(_("dup2(): %s\n"), strerror(errno)); + goto quit; + } + + pa_close(fd); + + } else if (optind+1 <= argc) { + pa_log(_("Too many arguments.\n")); + goto quit; } - if (!(optind >= argc)) { - if (optind+1 == argc) { - int fd; + if (!raw) { + SF_INFO sfi; + pa_zero(sfi); - if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { - fprintf(stderr, _("open(): %s\n"), strerror(errno)); + if (mode == RECORD) { + /* This might patch up the sample spec */ + if (pa_sndfile_write_sample_spec(&sfi, &sample_spec) < 0) { + pa_log(_("Failed to generate sample specification for file.\n")); goto quit; } - if (dup2(fd, mode == PLAYBACK ? 0 : 1) < 0) { - fprintf(stderr, _("dup2(): %s\n"), strerror(errno)); + /* 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)))) + file_format = SF_FORMAT_WAV; + else + file_format = SF_FORMAT_WAVEX; + } + + sfi.format |= file_format; + } + + if (!(sndfile = sf_open_fd(mode == RECORD ? STDOUT_FILENO : STDIN_FILENO, + mode == RECORD ? SFM_WRITE : SFM_READ, + &sfi, 0))) { + pa_log(_("Failed to open audio file.\n")); + goto quit; + } + + if (mode == PLAYBACK) { + if (sample_spec_set) + pa_log(_("Warning: specified sample specification will be overwritten with specification from file.\n")); + + if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { + pa_log(_("Failed to determine sample specification from file.\n")); goto quit; } + sample_spec_set = TRUE; + + if (!channel_map_set) { + /* Allow the user to overwrite the channel map on the command line */ + if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { + if (sample_spec.channels > 2) + pa_log(_("Warning: Failed to determine channel map from file.\n")); + } else + channel_map_set = TRUE; + } + } + } - close(fd); + if (!channel_map_set) + pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); - if (!stream_name) - stream_name = pa_xstrdup(argv[optind]); + if (!pa_channel_map_compatible(&channel_map, &sample_spec)) { + pa_log(_("Channel map doesn't match sample specification\n")); + goto quit; + } - } else { - fprintf(stderr, _("Too many arguments.\n")); - goto quit; + if (!raw) { + pa_proplist *sfp; + + if (mode == PLAYBACK) + readf_function = pa_sndfile_readf_function(&sample_spec); + else { + if (pa_sndfile_write_channel_map(sndfile, &channel_map) < 0) + pa_log(_("Warning: failed to write channel map to file.\n")); + + writef_function = pa_sndfile_writef_function(&sample_spec); } + + /* Fill in libsndfile prop list data */ + sfp = pa_proplist_new(); + pa_sndfile_init_proplist(sndfile, sfp); + pa_proplist_update(proplist, PA_UPDATE_MERGE, sfp); + pa_proplist_free(sfp); } - if (!client_name) - client_name = pa_xstrdup(bn); + if (verbose) { + char tss[PA_SAMPLE_SPEC_SNPRINT_MAX], tcm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_log(_("Opening a %s stream with sample specification '%s' and channel map '%s'.\n"), + mode == RECORD ? _("recording") : _("playback"), + pa_sample_spec_snprint(tss, sizeof(tss), &sample_spec), + pa_channel_map_snprint(tcm, sizeof(tcm), &channel_map)); + } - if (!stream_name) - stream_name = pa_xstrdup(client_name); + /* Fill in client name if none was set */ + if (!pa_proplist_contains(proplist, PA_PROP_APPLICATION_NAME)) { + char *t; + + if ((t = pa_locale_to_utf8(bn))) { + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t); + pa_xfree(t); + } + } + + /* Fill in media name if none was set */ + if (!pa_proplist_contains(proplist, PA_PROP_MEDIA_NAME)) { + const char *t; + + if ((t = filename) || + (t = pa_proplist_gets(proplist, PA_PROP_APPLICATION_NAME))) + pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t); + } /* Set up a new main loop */ if (!(m = pa_mainloop_new())) { - fprintf(stderr, _("pa_mainloop_new() failed.\n")); + pa_log(_("pa_mainloop_new() failed.\n")); goto quit; } mainloop_api = pa_mainloop_get_api(m); - r = pa_signal_init(mainloop_api); - assert(r == 0); + pa_assert_se(pa_signal_init(mainloop_api) == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); pa_signal_new(SIGTERM, exit_signal_callback, NULL); #ifdef SIGUSR1 pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL); #endif -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#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))) { - fprintf(stderr, _("io_new() failed.\n")); - goto quit; + pa_disable_sigpipe(); + + if (raw) { + 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))) { + pa_log(_("io_new() failed.\n")); + goto quit; + } } /* Create a new connection context */ - if (!(context = pa_context_new(mainloop_api, client_name))) { - fprintf(stderr, _("pa_context_new() failed.\n")); + if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { + pa_log(_("pa_context_new() failed.\n")); goto quit; } @@ -796,7 +1063,7 @@ int main(int argc, char *argv[]) { /* Connect the context */ if (pa_context_connect(context, server, 0, NULL) < 0) { - fprintf(stderr, _("pa_context_connect() failed: %s\n"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_context_connect() failed: %s\n"), pa_strerror(pa_context_errno(context))); goto quit; } @@ -807,14 +1074,14 @@ int main(int argc, char *argv[]) { pa_timeval_add(&tv, TIME_EVENT_USEC); if (!(time_event = mainloop_api->time_new(mainloop_api, &tv, time_event_callback, NULL))) { - fprintf(stderr, _("time_new() failed.\n")); + pa_log(_("time_new() failed.\n")); goto quit; } } /* Run the main loop */ if (pa_mainloop_run(m, &ret) < 0) { - fprintf(stderr, _("pa_mainloop_run() failed.\n")); + pa_log(_("pa_mainloop_run() failed.\n")); goto quit; } @@ -826,12 +1093,12 @@ quit: pa_context_unref(context); if (stdio_event) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->io_free(stdio_event); } if (time_event) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->time_free(time_event); } @@ -844,8 +1111,12 @@ quit: pa_xfree(server); pa_xfree(device); - pa_xfree(client_name); - pa_xfree(stream_name); + + if (sndfile) + sf_close(sndfile); + + if (proplist) + pa_proplist_free(proplist); return ret; } diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c index d94f2665..ac60a0bc 100644 --- a/src/utils/pacmd.c +++ b/src/utils/pacmd.c @@ -38,11 +38,13 @@ #include <pulse/xmalloc.h> #include <pulse/i18n.h> +#include <pulsecore/macro.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> #include <pulsecore/pid.h> int main(int argc, char*argv[]) { + pid_t pid ; int fd = -1; int ret = 1, i; @@ -56,7 +58,7 @@ int main(int argc, char*argv[]) { bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) { - pa_log("No PulseAudio daemon running, or not running as session daemon."); + pa_log(_("No PulseAudio daemon running, or not running as session daemon.")); goto fail; } @@ -65,7 +67,7 @@ int main(int argc, char*argv[]) { goto fail; } - memset(&sa, 0, sizeof(sa)); + pa_zero(sa); sa.sun_family = AF_UNIX; if (!(cli = pa_runtime_path("cli"))) @@ -147,9 +149,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(0, &ifds)) { ssize_t r; - assert(!ibuf_length); + pa_assert(!ibuf_length); - if ((r = read(0, ibuf, sizeof(ibuf))) <= 0) { + if ((r = pa_read(0, ibuf, sizeof(ibuf), NULL)) <= 0) { if (r < 0) { pa_log(_("read(): %s"), strerror(errno)); goto fail; @@ -164,9 +166,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(fd, &ifds)) { ssize_t r; - assert(!obuf_length); + pa_assert(!obuf_length); - if ((r = read(fd, obuf, sizeof(obuf))) <= 0) { + if ((r = pa_read(fd, obuf, sizeof(obuf), NULL)) <= 0) { if (r < 0) { pa_log(_("read(): %s"), strerror(errno)); goto fail; @@ -181,9 +183,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(1, &ofds)) { ssize_t r; - assert(obuf_length); + pa_assert(obuf_length); - if ((r = write(1, obuf + obuf_index, obuf_length)) < 0) { + if ((r = pa_write(1, obuf + obuf_index, obuf_length, NULL)) < 0) { pa_log(_("write(): %s"), strerror(errno)); goto fail; } @@ -195,9 +197,9 @@ int main(int argc, char*argv[]) { if (FD_ISSET(fd, &ofds)) { ssize_t r; - assert(ibuf_length); + pa_assert(ibuf_length); - if ((r = write(fd, ibuf + ibuf_index, ibuf_length)) < 0) { + if ((r = pa_write(fd, ibuf + ibuf_index, ibuf_length, NULL)) < 0) { pa_log(_("write(): %s"), strerror(errno)); goto fail; } @@ -207,14 +209,14 @@ int main(int argc, char*argv[]) { } if (ibuf_length <= 0 && ibuf_eof && !ibuf_closed) { - close(0); + pa_close(0); shutdown(fd, SHUT_WR); ibuf_closed = TRUE; } if (obuf_length <= 0 && obuf_eof && !obuf_closed) { shutdown(fd, SHUT_RD); - close(1); + pa_close(1); obuf_closed = TRUE; } } diff --git a/src/utils/pactl.c b/src/utils/pactl.c index de1c6d3d..53c67666 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -38,9 +38,13 @@ #include <pulse/i18n.h> #include <pulse/pulseaudio.h> + +#include <pulsecore/macro.h> #include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sndfile-util.h> -#define BUFSIZE 1024 +#define BUFSIZE (16*1024) static pa_context *context = NULL; static pa_mainloop_api *mainloop_api = NULL; @@ -48,16 +52,19 @@ static pa_mainloop_api *mainloop_api = NULL; static char *device = NULL, *sample_name = NULL, *sink_name = NULL, *source_name = NULL, *module_name = NULL, *module_args = NULL, *card_name = NULL, *profile_name = NULL; static uint32_t sink_input_idx = PA_INVALID_INDEX, source_output_idx = PA_INVALID_INDEX; static uint32_t module_index; -static int suspend; +static pa_bool_t suspend; + +static pa_proplist *proplist = NULL; static SNDFILE *sndfile = NULL; static pa_stream *sample_stream = NULL; static pa_sample_spec sample_spec; +static pa_channel_map channel_map; static size_t sample_length = 0; static int actions = 1; -static int nl = 0; +static pa_bool_t nl = FALSE; static enum { NONE, @@ -77,11 +84,10 @@ static enum { } action = NONE; static void quit(int ret) { - assert(mainloop_api); + pa_assert(mainloop_api); mainloop_api->quit(mainloop_api, ret); } - static void context_drain_complete(pa_context *c, void *userdata) { pa_context_disconnect(c); } @@ -94,9 +100,8 @@ static void drain(void) { pa_operation_unref(o); } - static void complete_action(void) { - assert(actions > 0); + pa_assert(actions > 0); if (!(--actions)) drain(); @@ -105,7 +110,7 @@ static void complete_action(void) { static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) { char s[128]; if (!i) { - fprintf(stderr, _("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get statistics: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -126,7 +131,7 @@ static void get_server_info_callback(pa_context *c, const pa_server_info *i, voi char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; if (!i) { - fprintf(stderr, _("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get server information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -175,7 +180,7 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sink information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -185,11 +190,11 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_ return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; printf(_("Sink #%u\n" "\tState: %s\n" @@ -255,7 +260,7 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get source information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -265,11 +270,11 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; printf(_("Source #%u\n" "\tState: %s\n" @@ -321,7 +326,7 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get module information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -331,13 +336,13 @@ static void get_module_info_callback(pa_context *c, const pa_module_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->n_used); + pa_snprintf(t, sizeof(t), "%u", i->n_used); printf(_("Module #%u\n" "\tName: %s\n" @@ -358,7 +363,7 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get client information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -368,13 +373,13 @@ static void get_client_info_callback(pa_context *c, const pa_client_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); printf(_("Client #%u\n" "\tDriver: %s\n" @@ -393,7 +398,7 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get card information: %s\n"), pa_strerror(pa_context_errno(c))); complete_action(); return; } @@ -403,13 +408,13 @@ static void get_card_info_callback(pa_context *c, const pa_card_info *i, int is_ return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); printf(_("Card #%u\n" "\tName: %s\n" @@ -442,7 +447,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sink input information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -452,14 +457,14 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); - snprintf(k, sizeof(k), "%u", i->client); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(k, sizeof(k), "%u", i->client); printf(_("Sink Input #%u\n" "\tDriver: %s\n" @@ -500,7 +505,7 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get source output information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -510,15 +515,15 @@ static void get_source_output_info_callback(pa_context *c, const pa_source_outpu return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; - snprintf(t, sizeof(t), "%u", i->owner_module); - snprintf(k, sizeof(k), "%u", i->client); + pa_snprintf(t, sizeof(t), "%u", i->owner_module); + pa_snprintf(k, sizeof(k), "%u", i->client); printf(_("Source Output #%u\n" "\tDriver: %s\n" @@ -551,7 +556,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int char *pl; if (is_last < 0) { - fprintf(stderr, _("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failed to get sample information: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -561,11 +566,11 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int return; } - assert(i); + pa_assert(i); if (nl) printf("\n"); - nl = 1; + nl = TRUE; pa_bytes_snprint(t, sizeof(t), i->bytes); @@ -599,7 +604,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int static void simple_callback(pa_context *c, int success, void *userdata) { if (!success) { - fprintf(stderr, _("Failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -609,7 +614,7 @@ static void simple_callback(pa_context *c, int success, void *userdata) { static void index_callback(pa_context *c, uint32_t idx, void *userdata) { if (idx == PA_INVALID_INDEX) { - fprintf(stderr, _("Failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Failure: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); return; } @@ -620,7 +625,7 @@ static void index_callback(pa_context *c, uint32_t idx, void *userdata) { } static void stream_state_callback(pa_stream *s, void *userdata) { - assert(s); + pa_assert(s); switch (pa_stream_get_state(s)) { case PA_STREAM_CREATING: @@ -633,7 +638,7 @@ static void stream_state_callback(pa_stream *s, void *userdata) { case PA_STREAM_FAILED: default: - fprintf(stderr, _("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + pa_log(_("Failed to upload sample: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); quit(1); } } @@ -641,16 +646,16 @@ static void stream_state_callback(pa_stream *s, void *userdata) { static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { sf_count_t l; float *d; - assert(s && length && sndfile); + pa_assert(s && length && sndfile); d = pa_xmalloc(length); - assert(sample_length >= length); + pa_assert(sample_length >= length); l = (sf_count_t) (length/pa_frame_size(&sample_spec)); if ((sf_readf_float(sndfile, d, l)) != l) { pa_xfree(d); - fprintf(stderr, _("Premature end of file\n")); + pa_log(_("Premature end of file\n")); quit(1); return; } @@ -666,7 +671,7 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { } static void context_state_callback(pa_context *c, void *userdata) { - assert(c); + pa_assert(c); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: @@ -691,7 +696,7 @@ static void context_state_callback(pa_context *c, void *userdata) { case UPLOAD_SAMPLE: sample_stream = pa_stream_new(c, sample_name, &sample_spec, NULL); - assert(sample_stream); + pa_assert(sample_stream); pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL); pa_stream_set_write_callback(sample_stream, stream_write_callback, NULL); @@ -749,7 +754,7 @@ static void context_state_callback(pa_context *c, void *userdata) { break; default: - assert(0); + pa_assert_not_reached(); } break; @@ -759,13 +764,13 @@ static void context_state_callback(pa_context *c, void *userdata) { case PA_CONTEXT_FAILED: default: - fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); + pa_log(_("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); quit(1); } } static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { - fprintf(stderr, _("Got SIGINT, exiting.\n")); + pa_log(_("Got SIGINT, exiting.\n")); quit(0); } @@ -791,13 +796,15 @@ static void help(const char *argv0) { argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0); } -enum { ARG_VERSION = 256 }; +enum { + ARG_VERSION = 256 +}; int main(int argc, char *argv[]) { pa_mainloop* m = NULL; char tmp[PATH_MAX]; - int ret = 1, r, c; - char *server = NULL, *client_name = NULL, *bn; + int ret = 1, c; + char *server = NULL, *bn; static const struct option long_options[] = { {"server", 1, NULL, 's'}, @@ -810,10 +817,9 @@ int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; + bn = pa_path_get_filename(argv[0]); + + proplist = pa_proplist_new(); while ((c = getopt_long(argc, argv, "s:n:h", long_options, NULL)) != -1) { switch (c) { @@ -837,32 +843,39 @@ int main(int argc, char *argv[]) { server = pa_xstrdup(optarg); break; - case 'n': - pa_xfree(client_name); - client_name = pa_xstrdup(optarg); + case 'n': { + char *t; + + if (!(t = pa_locale_to_utf8(optarg)) || + pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t) < 0) { + + pa_log(_("Invalid client name '%s'\n"), t ? t : optarg); + pa_xfree(t); + goto quit; + } + + pa_xfree(t); break; + } default: goto quit; } } - if (!client_name) - client_name = pa_xstrdup(bn); - if (optind < argc) { - if (!strcmp(argv[optind], "stat")) + if (pa_streq(argv[optind], "stat")) action = STAT; - else if (!strcmp(argv[optind], "exit")) + else if (pa_streq(argv[optind], "exit")) action = EXIT; - else if (!strcmp(argv[optind], "list")) + else if (pa_streq(argv[optind], "list")) action = LIST; - else if (!strcmp(argv[optind], "upload-sample")) { - struct SF_INFO sfinfo; + else if (pa_streq(argv[optind], "upload-sample")) { + struct SF_INFO sfi; action = UPLOAD_SAMPLE; if (optind+1 >= argc) { - fprintf(stderr, _("Please specify a sample file to load\n")); + pa_log(_("Please specify a sample file to load\n")); goto quit; } @@ -882,21 +895,31 @@ int main(int argc, char *argv[]) { sample_name = pa_xstrdup(tmp); } - memset(&sfinfo, 0, sizeof(sfinfo)); - if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfinfo))) { - fprintf(stderr, _("Failed to open sound file.\n")); + pa_zero(sfi); + if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfi))) { + pa_log(_("Failed to open sound file.\n")); goto quit; } + if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { + pa_log(_("Failed to determine sample specification from file.\n")); + goto quit; + } sample_spec.format = PA_SAMPLE_FLOAT32; - sample_spec.rate = (uint32_t) sfinfo.samplerate; - sample_spec.channels = (uint8_t) sfinfo.channels; - sample_length = (size_t)sfinfo.frames*pa_frame_size(&sample_spec); - } else if (!strcmp(argv[optind], "play-sample")) { + if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { + if (sample_spec.channels > 2) + pa_log(_("Warning: Failed to determine sample specification from file.\n")); + pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); + } + + pa_assert(pa_channel_map_compatible(&channel_map, &sample_spec)); + sample_length = (size_t) sfi.frames*pa_frame_size(&sample_spec); + + } else if (pa_streq(argv[optind], "play-sample")) { action = PLAY_SAMPLE; if (argc != optind+2 && argc != optind+3) { - fprintf(stderr, _("You have to specify a sample name to play\n")); + pa_log(_("You have to specify a sample name to play\n")); goto quit; } @@ -905,33 +928,36 @@ int main(int argc, char *argv[]) { if (optind+2 < argc) device = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "remove-sample")) { + } else if (pa_streq(argv[optind], "remove-sample")) { action = REMOVE_SAMPLE; if (argc != optind+2) { - fprintf(stderr, _("You have to specify a sample name to remove\n")); + pa_log(_("You have to specify a sample name to remove\n")); goto quit; } sample_name = pa_xstrdup(argv[optind+1]); - } else if (!strcmp(argv[optind], "move-sink-input")) { + + } else if (pa_streq(argv[optind], "move-sink-input")) { action = MOVE_SINK_INPUT; if (argc != optind+3) { - fprintf(stderr, _("You have to specify a sink input index and a sink\n")); + pa_log(_("You have to specify a sink input index and a sink\n")); goto quit; } sink_input_idx = (uint32_t) atoi(argv[optind+1]); sink_name = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "move-source-output")) { + + } else if (pa_streq(argv[optind], "move-source-output")) { action = MOVE_SOURCE_OUTPUT; if (argc != optind+3) { - fprintf(stderr, _("You have to specify a source output index and a source\n")); + pa_log(_("You have to specify a source output index and a source\n")); goto quit; } source_output_idx = (uint32_t) atoi(argv[optind+1]); source_name = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "load-module")) { + + } else if (pa_streq(argv[optind], "load-module")) { int i; size_t n = 0; char *p; @@ -939,7 +965,7 @@ int main(int argc, char *argv[]) { action = LOAD_MODULE; if (argc <= optind+1) { - fprintf(stderr, _("You have to specify a module name and arguments.\n")); + pa_log(_("You have to specify a module name and arguments.\n")); goto quit; } @@ -955,21 +981,21 @@ int main(int argc, char *argv[]) { p += sprintf(p, "%s%s", p == module_args ? "" : " ", argv[i]); } - } else if (!strcmp(argv[optind], "unload-module")) { + } else if (pa_streq(argv[optind], "unload-module")) { action = UNLOAD_MODULE; if (argc != optind+2) { - fprintf(stderr, _("You have to specify a module index\n")); + pa_log(_("You have to specify a module index\n")); goto quit; } module_index = (uint32_t) atoi(argv[optind+1]); - } else if (!strcmp(argv[optind], "suspend-sink")) { + } else if (pa_streq(argv[optind], "suspend-sink")) { action = SUSPEND_SINK; if (argc > optind+3 || optind+1 >= argc) { - fprintf(stderr, _("You may not specify more than one sink. You have to specify a boolean value.\n")); + pa_log(_("You may not specify more than one sink. You have to specify a boolean value.\n")); goto quit; } @@ -978,11 +1004,11 @@ int main(int argc, char *argv[]) { if (argc > optind+2) sink_name = pa_xstrdup(argv[optind+1]); - } else if (!strcmp(argv[optind], "suspend-source")) { + } else if (pa_streq(argv[optind], "suspend-source")) { action = SUSPEND_SOURCE; if (argc > optind+3 || optind+1 >= argc) { - fprintf(stderr, _("You may not specify more than one source. You have to specify a boolean value.\n")); + pa_log(_("You may not specify more than one source. You have to specify a boolean value.\n")); goto quit; } @@ -990,18 +1016,18 @@ int main(int argc, char *argv[]) { if (argc > optind+2) source_name = pa_xstrdup(argv[optind+1]); - } else if (!strcmp(argv[optind], "set-card-profile")) { + } else if (pa_streq(argv[optind], "set-card-profile")) { action = SET_CARD_PROFILE; if (argc != optind+3) { - fprintf(stderr, _("You have to specify a card name/index and a profile name\n")); + pa_log(_("You have to specify a card name/index and a profile name\n")); goto quit; } card_name = pa_xstrdup(argv[optind+1]); profile_name = pa_xstrdup(argv[optind+2]); - } else if (!strcmp(argv[optind], "help")) { + } else if (pa_streq(argv[optind], "help")) { help(bn); ret = 0; goto quit; @@ -1009,37 +1035,35 @@ int main(int argc, char *argv[]) { } if (action == NONE) { - fprintf(stderr, _("No valid command specified.\n")); + pa_log(_("No valid command specified.\n")); goto quit; } if (!(m = pa_mainloop_new())) { - fprintf(stderr, _("pa_mainloop_new() failed.\n")); + pa_log(_("pa_mainloop_new() failed.\n")); goto quit; } mainloop_api = pa_mainloop_get_api(m); - r = pa_signal_init(mainloop_api); - assert(r == 0); + pa_assert_se(pa_signal_init(mainloop_api) == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif + pa_signal_new(SIGTERM, exit_signal_callback, NULL); + pa_disable_sigpipe(); - if (!(context = pa_context_new(mainloop_api, client_name))) { - fprintf(stderr, _("pa_context_new() failed.\n")); + if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { + pa_log(_("pa_context_new() failed.\n")); goto quit; } pa_context_set_state_callback(context, context_state_callback, NULL); if (pa_context_connect(context, server, 0, NULL) < 0) { - fprintf(stderr, _("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); + pa_log(_("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); goto quit; } if (pa_mainloop_run(m, &ret) < 0) { - fprintf(stderr, _("pa_mainloop_run() failed.\n")); + pa_log(_("pa_mainloop_run() failed.\n")); goto quit; } @@ -1055,18 +1079,20 @@ quit: pa_mainloop_free(m); } - if (sndfile) - sf_close(sndfile); - pa_xfree(server); pa_xfree(device); pa_xfree(sample_name); pa_xfree(sink_name); pa_xfree(source_name); pa_xfree(module_args); - pa_xfree(client_name); pa_xfree(card_name); pa_xfree(profile_name); + if (sndfile) + sf_close(sndfile); + + if (proplist) + pa_proplist_free(proplist); + return ret; } diff --git a/src/utils/paplay.c b/src/utils/paplay.c deleted file mode 100644 index f6ba6f6d..00000000 --- a/src/utils/paplay.c +++ /dev/null @@ -1,435 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2.1 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <signal.h> -#include <string.h> -#include <errno.h> -#include <unistd.h> -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> -#include <getopt.h> -#include <locale.h> - -#include <sndfile.h> - -#include <pulse/pulseaudio.h> -#include <pulse/i18n.h> - -static pa_context *context = NULL; -static pa_stream *stream = NULL; -static pa_mainloop_api *mainloop_api = NULL; - -static char *stream_name = NULL, *client_name = NULL, *device = NULL; - -static int verbose = 0; -static pa_volume_t volume = PA_VOLUME_NORM; - -static SNDFILE* sndfile = NULL; - -static pa_sample_spec sample_spec = { 0, 0, 0 }; -static pa_channel_map channel_map; -static int channel_map_set = 0; - -static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL; - -/* A shortcut for terminating the application */ -static void quit(int ret) { - assert(mainloop_api); - mainloop_api->quit(mainloop_api, ret); -} - -/* Connection draining complete */ -static void context_drain_complete(pa_context*c, void *userdata) { - pa_context_disconnect(c); -} - -/* Stream draining complete */ -static void stream_drain_complete(pa_stream*s, int success, void *userdata) { - pa_operation *o; - - if (!success) { - fprintf(stderr, _("Failed to drain stream: %s\n"), pa_strerror(pa_context_errno(context))); - quit(1); - } - - if (verbose) - fprintf(stderr, _("Playback stream drained.\n")); - - pa_stream_disconnect(stream); - pa_stream_unref(stream); - stream = NULL; - - if (!(o = pa_context_drain(context, context_drain_complete, NULL))) - pa_context_disconnect(context); - else { - pa_operation_unref(o); - - if (verbose) - fprintf(stderr, _("Draining connection to server.\n")); - } -} - -/* This is called whenever new data may be written to the stream */ -static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { - sf_count_t bytes; - void *data; - assert(s && length); - - if (!sndfile) - return; - - data = pa_xmalloc(length); - - if (readf_function) { - size_t k = pa_frame_size(&sample_spec); - - if ((bytes = readf_function(sndfile, data, (sf_count_t) (length/k))) > 0) - bytes *= (sf_count_t) k; - - } else - bytes = sf_read_raw(sndfile, data, (sf_count_t) length); - - if (bytes > 0) - pa_stream_write(s, data, (size_t) bytes, pa_xfree, 0, PA_SEEK_RELATIVE); - else - pa_xfree(data); - - if (bytes < (sf_count_t) length) { - sf_close(sndfile); - sndfile = NULL; - pa_operation_unref(pa_stream_drain(s, stream_drain_complete, NULL)); - } -} - -/* This routine is called whenever the stream state changes */ -static void stream_state_callback(pa_stream *s, void *userdata) { - assert(s); - - switch (pa_stream_get_state(s)) { - case PA_STREAM_CREATING: - case PA_STREAM_TERMINATED: - break; - - case PA_STREAM_READY: - if (verbose) - fprintf(stderr, _("Stream successfully created\n")); - break; - - case PA_STREAM_FAILED: - default: - fprintf(stderr, _("Stream errror: %s\n"), pa_strerror(pa_context_errno(pa_stream_get_context(s)))); - quit(1); - } -} - -/* This is called whenever the context status changes */ -static void context_state_callback(pa_context *c, void *userdata) { - assert(c); - - switch (pa_context_get_state(c)) { - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - - case PA_CONTEXT_READY: { - pa_cvolume cv; - - assert(c && !stream); - - if (verbose) - fprintf(stderr, _("Connection established.\n")); - - stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL); - assert(stream); - - pa_stream_set_state_callback(stream, stream_state_callback, NULL); - pa_stream_set_write_callback(stream, stream_write_callback, NULL); - pa_stream_connect_playback(stream, device, NULL, 0, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL); - - break; - } - - case PA_CONTEXT_TERMINATED: - quit(0); - break; - - case PA_CONTEXT_FAILED: - default: - fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); - quit(1); - } -} - -/* UNIX signal to quit recieved */ -static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { - if (verbose) - fprintf(stderr, _("Got SIGINT, exiting.\n")); - quit(0); - -} - -static void help(const char *argv0) { - - printf(_("%s [options] [FILE]\n\n" - " -h, --help Show this help\n" - " --version Show version\n\n" - " -v, --verbose Enable verbose operation\n\n" - " -s, --server=SERVER The name of the server to connect to\n" - " -d, --device=DEVICE The name of the sink to connect to\n" - " -n, --client-name=NAME How to call this client on the server\n" - " --stream-name=NAME How to call this stream on the server\n" - " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" - " --channel-map=CHANNELMAP Set the channel map to the use\n"), - argv0); -} - -enum { - ARG_VERSION = 256, - ARG_STREAM_NAME, - ARG_VOLUME, - ARG_CHANNELMAP -}; - -int main(int argc, char *argv[]) { - pa_mainloop* m = NULL; - int ret = 1, r, c; - char *bn, *server = NULL; - const char *filename; - SF_INFO sfinfo; - - static const struct option long_options[] = { - {"device", 1, NULL, 'd'}, - {"server", 1, NULL, 's'}, - {"client-name", 1, NULL, 'n'}, - {"stream-name", 1, NULL, ARG_STREAM_NAME}, - {"version", 0, NULL, ARG_VERSION}, - {"help", 0, NULL, 'h'}, - {"verbose", 0, NULL, 'v'}, - {"volume", 1, NULL, ARG_VOLUME}, - {"channel-map", 1, NULL, ARG_CHANNELMAP}, - {NULL, 0, NULL, 0} - }; - - setlocale(LC_ALL, ""); - bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); - - if (!(bn = strrchr(argv[0], '/'))) - bn = argv[0]; - else - bn++; - - while ((c = getopt_long(argc, argv, "d:s:n:hv", long_options, NULL)) != -1) { - - switch (c) { - case 'h' : - help(bn); - ret = 0; - goto quit; - - case ARG_VERSION: - printf(_("paplay %s\nCompiled with libpulse %s\n" - "Linked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version()); - ret = 0; - goto quit; - - case 'd': - pa_xfree(device); - device = pa_xstrdup(optarg); - break; - - case 's': - pa_xfree(server); - server = pa_xstrdup(optarg); - break; - - case 'n': - pa_xfree(client_name); - client_name = pa_xstrdup(optarg); - break; - - case ARG_STREAM_NAME: - pa_xfree(stream_name); - stream_name = pa_xstrdup(optarg); - break; - - case 'v': - verbose = 1; - break; - - case ARG_VOLUME: { - int v = atoi(optarg); - volume = v < 0 ? 0U : (pa_volume_t) v; - break; - } - - case ARG_CHANNELMAP: - if (!pa_channel_map_parse(&channel_map, optarg)) { - fprintf(stderr, _("Invalid channel map\n")); - goto quit; - } - - channel_map_set = 1; - break; - - default: - goto quit; - } - } - - filename = optind < argc ? argv[optind] : "STDIN"; - - memset(&sfinfo, 0, sizeof(sfinfo)); - - if (optind < argc) - sndfile = sf_open(filename, SFM_READ, &sfinfo); - else - sndfile = sf_open_fd(STDIN_FILENO, SFM_READ, &sfinfo, 0); - - if (!sndfile) { - fprintf(stderr, _("Failed to open file '%s'\n"), filename); - goto quit; - } - - sample_spec.rate = (uint32_t) sfinfo.samplerate; - sample_spec.channels = (uint8_t) sfinfo.channels; - - readf_function = NULL; - - switch (sfinfo.format & 0xFF) { - case SF_FORMAT_PCM_16: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_PCM_S8: - sample_spec.format = PA_SAMPLE_S16NE; - readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_short; - break; - - case SF_FORMAT_ULAW: - sample_spec.format = PA_SAMPLE_ULAW; - break; - - case SF_FORMAT_ALAW: - sample_spec.format = PA_SAMPLE_ALAW; - break; - - case SF_FORMAT_FLOAT: - case SF_FORMAT_DOUBLE: - default: - sample_spec.format = PA_SAMPLE_FLOAT32NE; - readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_float; - break; - } - - assert(pa_sample_spec_valid(&sample_spec)); - - if (channel_map_set && channel_map.channels != sample_spec.channels) { - fprintf(stderr, _("Channel map doesn't match file.\n")); - goto quit; - } - - if (!client_name) { - client_name = pa_locale_to_utf8(bn); - if (!client_name) - client_name = pa_utf8_filter(bn); - } - - if (!stream_name) { - const char *n; - - n = sf_get_string(sndfile, SF_STR_TITLE); - - if (!n) - n = filename; - - stream_name = pa_locale_to_utf8(n); - if (!stream_name) - stream_name = pa_utf8_filter(n); - } - - if (verbose) { - char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; - pa_sample_spec_snprint(t, sizeof(t), &sample_spec); - fprintf(stderr, _("Using sample spec '%s'\n"), t); - } - - /* Set up a new main loop */ - if (!(m = pa_mainloop_new())) { - fprintf(stderr, _("pa_mainloop_new() failed.\n")); - goto quit; - } - - mainloop_api = pa_mainloop_get_api(m); - - r = pa_signal_init(mainloop_api); - assert(r == 0); - pa_signal_new(SIGINT, exit_signal_callback, NULL); -#ifdef SIGPIPE - signal(SIGPIPE, SIG_IGN); -#endif - - /* Create a new connection context */ - if (!(context = pa_context_new(mainloop_api, client_name))) { - fprintf(stderr, _("pa_context_new() failed.\n")); - goto quit; - } - - pa_context_set_state_callback(context, context_state_callback, NULL); - - /* Connect the context */ - if (pa_context_connect(context, server, 0, NULL) < 0) { - fprintf(stderr, _("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); - goto quit; - } - - /* Run the main loop */ - if (pa_mainloop_run(m, &ret) < 0) { - fprintf(stderr, _("pa_mainloop_run() failed.\n")); - goto quit; - } - -quit: - if (stream) - pa_stream_unref(stream); - - if (context) - pa_context_unref(context); - - if (m) { - pa_signal_done(); - pa_mainloop_free(m); - } - - pa_xfree(server); - pa_xfree(device); - pa_xfree(client_name); - pa_xfree(stream_name); - - if (sndfile) - sf_close(sndfile); - - return ret; -} |