Wednesday, July 9, 2008

More PulseAudio Problems

I reinstalled PulseAudio to try and resolve the plethora of problems I've been having with it (that and openSUSE 11 doesn't actually ship th esound daemon anymore, so if I want full audio support I have no choice but to get the pulse-esound-compat package working short of forking the distro and maintaining my own set of esound/gnome/etc packages which is not my idea of fun).

Unfortunately, this has resulted in countless hours of frustration. After having read and followed the instructions at http://www.pulseaudio.org/wiki/PerfectSetup, it turns out that is exactly how my system was already configured. So no help there.

I don't know what the deal is, but I constantly have problems with PulseAudio.

For example, earlier today I was having an IM conversation with a friend in Pidgin and it locked up. I restarted Pidgin, but I wasn't getting audio events anymore. *shrug* Oh well.

At some point later I navigated my firefox window over to youtube to link a friend to some video. No surprise here, I get no sound and in fact, the flash video pauses as soon as the sound was supposed to start (which is a few seconds into the video).

At this point I close firefox and decide to reload it, thinking maybe that will solve it. Nope, I was wrong. Instead, firefox hangs before any windows pop up. Great.

Since I got flamed last time I blogged about PulseAudio for issuing a `killall -9 pulseaudio` command (btw, pulseaudio appears to have a --kill command-line option), I figured I'd log out and log back in again instead... but this was not to be. Instead, my desktop locks up (presumably because of the logout.wav).

Ok... so I Ctrl+Alt+F1 to the console, login as root, `init 3; init 5`. Since my system is configured to auto-login, when X came back up it tried to log me in. Instead, I get a hang.

Good grief, I guess I have no option but to reboot so that's what I do.

In the interest of saving other people from this catastrophe, I grabbed libgnome out of svn and wrote up the following patch to prevent it from hanging whenever PulseAudio decides to misbehave (which it seems quite prone to do).

Index: libgnome/gnome-sound.c
===================================================================
--- libgnome/gnome-sound.c (revision 3750)
+++ libgnome/gnome-sound.c (working copy)
@@ -30,8 +30,12 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/types.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
 #include <time.h>
+#include <poll.h>
 
 #ifdef HAVE_ESD
 #include <esd.h>
@@ -413,6 +417,55 @@
 }
 #endif
 
+#ifdef HAVE_ESD
+static int
+send_all (int fd, const char *buf, size_t buflen)
+{
+ struct pollfd pfd[1];
+ size_t nwritten = 0;
+ int flags, rv;
+ ssize_t n;
+ 
+ if ((flags = fcntl (fd, F_GETFL)) == -1)
+  return -1;
+ 
+ fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+ 
+ pfd[0].events = POLLOUT;
+ pfd[0].fd = fd;
+ 
+ do {
+  do {
+   pfd[0].revents = 0;
+   rv = poll (pfd, 1, 100);
+  } while (rv == -1 && errno == EINTR);
+  
+  if (pfd[0].revents & POLLOUT) {
+   /* socket is ready for writing */
+   do {
+    n = write (fd, buf + nwritten, buflen - nwritten);
+   } while (n == -1 && errno == EINTR);
+   
+   if (n > 0)
+    nwritten += n;
+  } else if (pfd[0].revents & (POLLERR | POLLHUP)) {
+   /* we /just/ lost the esd connection */
+   esd_close (fd);
+   fd = -1;
+   break;
+  } else if (rv == -1 && errno == EBADF) {
+   /* socket is bad */
+   fd = -1;
+   break;
+  }
+ } while (nwritten < buflen);
+ 
+ if (fd != -1 && flags != -1)
+  fcntl (fd, F_SETFL, flags);
+ 
+ return fd;
+}
+#endif
 
 /**
  * gnome_sound_sample_load:
@@ -469,21 +522,23 @@
     * file, or event type, for later identification */
    s->id = esd_sample_cache (gnome_sound_connection, s->format, s->rate,
         size, (char *)sample_name);
-   write (gnome_sound_connection, s->data, size);
-   confirm = esd_confirm_sample_cache (gnome_sound_connection);
+   
+   gnome_sound_connection = send_all (gnome_sound_connection, (const char *) s->data, size);
+   if (gnome_sound_connection != -1)
+     confirm = esd_confirm_sample_cache (gnome_sound_connection);
+   
    if (s->id <= 0 || confirm != s->id)
      {
        g_warning ("error caching sample <%d>!\n", s->id);
        s->id = 0;
      }
-   g_free (s->data);
-   s->data = NULL;
  }
     }
 
   sample_id = s->id;
 
-  g_free(s->data); g_free(s);
+  g_free(s->data);
+  g_free(s);
 
   return sample_id;
 #else
@@ -521,9 +576,12 @@
 
   sample = gnome_sound_sample_load (buf, filename);
 
-  esd_sample_play(gnome_sound_connection, sample);
-  fsync (gnome_sound_connection);
-  esd_sample_free(gnome_sound_connection, sample);
+  if (gnome_sound_connection != -1 && sample > 0)
+    {
+      esd_sample_play(gnome_sound_connection, sample);
+      fsync (gnome_sound_connection);
+      esd_sample_free(gnome_sound_connection, sample);
+    }
 #endif
 }

This patch should hopefully help prevent the typical gnome apps from hanging when trying to play audio, but I don't really have any surefire way of testing that it actually works.

Update 2008-07-10 10:42am I've now also written the following patch for libesd so that it doesn't hang for god knows how long when trying to open a connection to the sound server (in my case, pulse-esound-compat):

diff -up esound-0.2.38.orig/esdlib.c esound-0.2.38/esdlib.c
--- esound-0.2.38.orig/esdlib.c 2008-07-10 10:10:31.000000000 -0400
+++ esound-0.2.38/esdlib.c 2008-07-10 10:32:23.000000000 -0400
@@ -20,9 +20,11 @@
 #include <arpa/inet.h>
 #include <errno.h>
 #include <sys/wait.h>
+#include <poll.h>
 
 #include <sys/un.h>
 
+
 #ifndef SUN_LEN
 #define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path)  \
                      + strlen ((ptr)->sun_path))
@@ -408,6 +410,43 @@ int esd_resume( int esd )
     return ok;
 }
 
+static int
+connect_timeout (int sockfd, const struct sockaddr *serv_addr, size_t addrlen, int timeout)
+{
+ struct pollfd pfd[1];
+ int flags, rv;
+ 
+ if ((flags = fcntl (sockfd, F_GETFL)) == -1)
+  return -1;
+ 
+ fcntl (sockfd, F_SETFL, flags | O_NONBLOCK);
+ 
+ if (connect (sockfd, serv_addr, addrlen) == 0) {
+  fcntl (sockfd, F_SETFL, flags);
+  return 0;
+ }
+ 
+ if (errno != EINPROGRESS)
+  return -1;
+ 
+ pfd[0].fd = sockfd;
+ pfd[0].events = POLLIN | POLLOUT;
+ 
+ do {
+  pfd[0].revents = 0;
+  rv = poll (pfd, 1, timeout);
+ } while (rv == -1 && errno == EINTR);
+ 
+ if (pfd[0].revents & (POLLIN | POLLOUT)) {
+  /* success, we are now connected */
+  fcntl (sockfd, F_SETFL, flags);
+  return 0;
+ }
+ 
+ /* took too long / error connecting, either way: epic fail */
+ return -1;
+}
+
 /**
  * esd_connect_tcpip: make a TCPIP connection to ESD
  * @host: specifies hostname and port to connect to as "hostname:port"
@@ -512,7 +551,7 @@ esd_connect_tcpip(const char *host)
            goto error_out;
          }
 
-         if ( connect( socket_out, res->ai_addr, res->ai_addrlen ) != -1 ) 
+         if ( connect_timeout ( socket_out, res->ai_addr, res->ai_addrlen, 1000 ) != -1 ) 
            break;
 
          close ( socket_out );
@@ -596,9 +635,9 @@ esd_connect_tcpip(const char *host)
     socket_addr.sin_family = AF_INET;
     socket_addr.sin_port = htons( port );
   
-    if ( connect( socket_out,
-    (struct sockaddr *) &socket_addr,
-    sizeof(struct sockaddr_in) ) < 0 )
+    if ( connect_timeout ( socket_out,
+      (struct sockaddr *) &socket_addr,
+      sizeof(struct sockaddr_in), 1000 ) < 0 )
  goto error_out;
 
     }
@@ -650,8 +689,7 @@ esd_connect_unix(void)
     socket_unix.sun_family = AF_UNIX;
     strncpy(socket_unix.sun_path, ESD_UNIX_SOCKET_NAME, sizeof(socket_unix.sun_path));
   
-    if ( connect( socket_out,
-    (struct sockaddr *) &socket_unix, SUN_LEN(&socket_unix) ) < 0 )
+    if ( connect_timeout ( socket_out, (struct sockaddr *) &socket_unix, SUN_LEN(&socket_unix), 100 ) < 0 )
  goto error_out;
   
     return socket_out;

Hopefully after these 2 patches get applied, my system at least won't become unusably hung when pulseaudio decides to crap out on me, but these patches doesn't solve the root of the problem :(

Post a Comment

Code Snippet Licensing

All code posted to this blog is licensed under the MIT/X11 license unless otherwise stated in the post itself.