Bug 635918 Part 1 - Make nsISound::Play use libcanberra on Linux rather than esound r=kinetik f=karlt
authorChris Coulson <chris.coulson@canonical.com>
Wed, 11 Jan 2012 11:37:36 +1300
changeset 85456 9a6371bdb362e27f7afb54f23e494e8eaa1c00a6
parent 85455 6571d631dd40922a6ba6b25dcd0ddb8460894ebd
child 85457 912022fc07417a32adb048fee36fd93ab35d73c0
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs635918
milestone12.0a1
Bug 635918 Part 1 - Make nsISound::Play use libcanberra on Linux rather than esound r=kinetik f=karlt
widget/gtk2/nsSound.cpp
--- a/widget/gtk2/nsSound.cpp
+++ b/widget/gtk2/nsSound.cpp
@@ -47,78 +47,126 @@
 #include "nsSound.h"
 
 #include "nsIURL.h"
 #include "nsIFileURL.h"
 #include "nsNetUtil.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "mozilla/FileUtils.h"
 
 #include <stdio.h>
 #include <unistd.h>
 
 #include <gtk/gtk.h>
-static PRLibrary *elib = nsnull;
 static PRLibrary *libcanberra = nsnull;
-static PRLibrary* libasound = nsnull;
-
-// the following from esd.h
-
-#define ESD_BITS8  (0x0000)
-#define ESD_BITS16 (0x0001) 
-#define ESD_MONO (0x0010)
-#define ESD_STEREO (0x0020) 
-#define ESD_STREAM (0x0000)
-#define ESD_PLAY (0x1000)
 
-#define WAV_MIN_LENGTH 44
+/* used to play sounds with libcanberra. */
+typedef struct _ca_context ca_context;
+typedef struct _ca_proplist ca_proplist;
 
-/* used to play the sounds from the find symbol call */
-typedef int  (*EsdPlayStreamType) (int, int, const char *, const char *);
-typedef int  (*EsdAudioOpenType)  (void);
-typedef int  (*EsdAudioWriteType) (const void *, int);
-typedef void (*EsdAudioCloseType) (void);
-
-/* used to find and play common system event sounds.
-   this interfaces with libcanberra.
- */
-typedef struct _ca_context ca_context;
+typedef void (*ca_finish_callback_t) (ca_context *c,
+                                      uint32_t id,
+                                      int error_code,
+                                      void *userdata);
 
 typedef int (*ca_context_create_fn) (ca_context **);
 typedef int (*ca_context_destroy_fn) (ca_context *);
 typedef int (*ca_context_play_fn) (ca_context *c,
                                    uint32_t id,
                                    ...);
 typedef int (*ca_context_change_props_fn) (ca_context *c,
                                            ...);
+typedef int (*ca_proplist_create_fn) (ca_proplist **);
+typedef int (*ca_proplist_destroy_fn) (ca_proplist *);
+typedef int (*ca_proplist_sets_fn) (ca_proplist *c,
+                                    const char *key,
+                                    const char *value);
+typedef int (*ca_context_play_full_fn) (ca_context *c,
+                                        uint32_t id,
+                                        ca_proplist *p,
+                                        ca_finish_callback_t cb,
+                                        void *userdata);
 
 static ca_context_create_fn ca_context_create;
 static ca_context_destroy_fn ca_context_destroy;
 static ca_context_play_fn ca_context_play;
 static ca_context_change_props_fn ca_context_change_props;
+static ca_proplist_create_fn ca_proplist_create;
+static ca_proplist_destroy_fn ca_proplist_destroy;
+static ca_proplist_sets_fn ca_proplist_sets;
+static ca_context_play_full_fn ca_context_play_full;
 
-/* we provide a blank error handler to silence ALSA's stderr
-   messages on computers with no sound devices */
-typedef void (*snd_lib_error_handler_t) (const char* file,
-                                         int         line,
-                                         const char* function,
-                                         int         err,
-                                         const char* format,
-                                         ...);
-typedef int (*snd_lib_error_set_handler_fn) (snd_lib_error_handler_t handler);
+struct ScopedCanberraFile {
+    ScopedCanberraFile(nsILocalFile *file): mFile(file) {};
+
+    ~ScopedCanberraFile() {
+        if (mFile) {
+            mFile->Remove(PR_FALSE);
+        }
+    }
+
+    void forget() {
+        mFile.forget();
+    }
+    nsILocalFile* operator->() { return mFile; }
+    operator nsILocalFile*() { return mFile; }
+
+    nsCOMPtr<nsILocalFile> mFile;
+};
+
+static ca_context*
+ca_context_get_default()
+{
+    // This allows us to avoid race conditions with freeing the context by handing that
+    // responsibility to Glib, and still use one context at a time
+    static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT;
+
+    ca_context* ctx = (ca_context*) g_static_private_get(&ctx_static_private);
+
+    if (ctx) {
+        return ctx;
+    }
+
+    ca_context_create(&ctx);
+    if (!ctx) {
+        return nsnull;
+    }
+
+    g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy);
+
+    GtkSettings* settings = gtk_settings_get_default();
+    if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+                                     "gtk-sound-theme-name")) {
+        gchar* sound_theme_name = nsnull;
+        g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name, NULL);
+
+        if (sound_theme_name) {
+            ca_context_change_props(ctx, "canberra.xdg-theme.name", sound_theme_name, NULL);
+            g_free(sound_theme_name);
+        }
+    }
+
+    return ctx;
+}
 
 static void
-quiet_error_handler(const char* file,
-                    int         line,
-                    const char* function,
-                    int         err,
-                    const char* format,
-                    ...)
+ca_finish_cb(ca_context *c,
+             uint32_t id,
+             int error_code,
+             void *userdata)
 {
+    nsILocalFile *file = reinterpret_cast<nsILocalFile *>(userdata);
+    if (file) {
+        file->Remove(PR_FALSE);
+        NS_RELEASE(file);
+    }
 }
 
 NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
 
 ////////////////////////////////////////////////////////////////////////
 nsSound::nsSound()
 {
     mInited = false;
@@ -133,76 +181,54 @@ nsSound::Init()
 {
     // This function is designed so that no library is compulsory, and
     // one library missing doesn't cause the other(s) to not be used.
     if (mInited) 
         return NS_OK;
 
     mInited = true;
 
-    if (!elib) {
-        elib = PR_LoadLibrary("libesd.so.0");
-    }
-
-    if (!libasound) {
-        PRFuncPtr func = PR_FindFunctionSymbolAndLibrary("snd_lib_error_set_handler",
-                                                         &libasound);
-        if (libasound) {
-            snd_lib_error_set_handler_fn snd_lib_error_set_handler =
-                 (snd_lib_error_set_handler_fn) func;
-            snd_lib_error_set_handler(quiet_error_handler);
-        }
-    }
-
     if (!libcanberra) {
         libcanberra = PR_LoadLibrary("libcanberra.so.0");
         if (libcanberra) {
             ca_context_create = (ca_context_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_create");
             if (!ca_context_create) {
                 PR_UnloadLibrary(libcanberra);
                 libcanberra = nsnull;
             } else {
                 // at this point we know we have a good libcanberra library
                 ca_context_destroy = (ca_context_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_destroy");
                 ca_context_play = (ca_context_play_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play");
                 ca_context_change_props = (ca_context_change_props_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_change_props");
+                ca_proplist_create = (ca_proplist_create_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_create");
+                ca_proplist_destroy = (ca_proplist_destroy_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_destroy");
+                ca_proplist_sets = (ca_proplist_sets_fn) PR_FindFunctionSymbol(libcanberra, "ca_proplist_sets");
+                ca_context_play_full = (ca_context_play_full_fn) PR_FindFunctionSymbol(libcanberra, "ca_context_play_full");
             }
         }
     }
 
     return NS_OK;
 }
 
 /* static */ void
 nsSound::Shutdown()
 {
-    if (elib) {
-        PR_UnloadLibrary(elib);
-        elib = nsnull;
-    }
     if (libcanberra) {
         PR_UnloadLibrary(libcanberra);
         libcanberra = nsnull;
     }
-    if (libasound) {
-        PR_UnloadLibrary(libasound);
-        libasound = nsnull;
-    }
 }
 
-#define GET_WORD(s, i) (s[i+1] << 8) | s[i]
-#define GET_DWORD(s, i) (s[i+3] << 24) | (s[i+2] << 16) | (s[i+1] << 8) | s[i]
-
 NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
                                         nsISupports *context,
                                         nsresult aStatus,
                                         PRUint32 dataLen,
                                         const PRUint8 *data)
 {
-
     // print a load error on bad status, and return
     if (NS_FAILED(aStatus)) {
 #ifdef DEBUG
         if (aLoader) {
             nsCOMPtr<nsIRequest> request;
             aLoader->GetRequest(getter_AddRefs(request));
             if (request) {
                 nsCOMPtr<nsIURI> uri;
@@ -216,252 +242,137 @@ NS_IMETHODIMP nsSound::OnStreamComplete(
                       }
                 }
             }
         }
 #endif
         return aStatus;
     }
 
-    int fd, mask = 0;
-    PRUint32 samples_per_sec = 0, avg_bytes_per_sec = 0, chunk_len = 0;
-    PRUint16 format, channels = 1, bits_per_sample = 0;
-    const PRUint8 *audio = nsnull;
-    size_t audio_len = 0;
-
-    if (dataLen < 4) {
-        NS_WARNING("Sound stream too short to determine its type");
-        return NS_ERROR_FAILURE;
-    }
+    nsCOMPtr<nsILocalFile> tmpFile;
+    nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsILocalFile),
+                                      getter_AddRefs(tmpFile));
 
-    if (memcmp(data, "RIFF", 4)) {
-#ifdef DEBUG
-        printf("We only support WAV files currently.\n");
-#endif
-        return NS_ERROR_FAILURE;
-    }
-
-    if (dataLen <= WAV_MIN_LENGTH) {
-        NS_WARNING("WAV files should be longer than 44 bytes.");
-        return NS_ERROR_FAILURE;
+    nsresult rv = tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample"));
+    if (NS_FAILED(rv)) {
+        return rv;
     }
 
-    PRUint32 i = 12;
-    while (i + 7 < dataLen) {
-        if (!memcmp(data + i, "fmt ", 4) && !chunk_len) {
-            i += 4;
-
-            /* length of the rest of this subblock (should be 16 for PCM data */
-            chunk_len = GET_DWORD(data, i);
-            i += 4;
-
-            if (chunk_len < 16 || i + chunk_len >= dataLen) {
-                NS_WARNING("Invalid WAV file: bad fmt chunk.");
-                return NS_ERROR_FAILURE;
-            }
-
-            format = GET_WORD(data, i);
-            i += 2;
-
-            channels = GET_WORD(data, i);
-            i += 2;
-
-            samples_per_sec = GET_DWORD(data, i);
-            i += 4;
-
-            avg_bytes_per_sec = GET_DWORD(data, i);
-            i += 4;
-
-            // block align
-            i += 2;
+    rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
 
-            bits_per_sample = GET_WORD(data, i);
-            i += 2;
-
-            /* we don't support WAVs with odd compression codes */
-            if (chunk_len != 16)
-                NS_WARNING("Extra format bits found in WAV. Ignoring");
-
-            i += chunk_len - 16;
-        } else if (!memcmp(data + i, "data", 4)) {
-            i += 4;
-            if (!chunk_len) {
-                NS_WARNING("Invalid WAV file: no fmt chunk found");
-                return NS_ERROR_FAILURE;
-            }
+    ScopedCanberraFile canberraFile(tmpFile);
 
-            audio_len = GET_DWORD(data, i);
-            i += 4;
-
-            /* try to play truncated WAVs */
-            if (i + audio_len > dataLen)
-                audio_len = dataLen - i;
-
-            audio = data + i;
-            break;
-        } else {
-            i += 4;
-            i += GET_DWORD(data, i);
-            i += 4;
-        }
+    mozilla::AutoFDClose fd;
+    rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR, &fd);
+    if (NS_FAILED(rv)) {
+        return rv;
     }
 
-    if (!audio) {
-        NS_WARNING("Invalid WAV file: no data chunk found");
-        return NS_ERROR_FAILURE;
+    // XXX: Should we do this on another thread?
+    PRUint32 length = dataLen;
+    while (length > 0) {
+        PRInt32 amount = PR_Write(fd, data, length);
+        if (amount < 0) {
+            return NS_ERROR_FAILURE;
+        }
+        length -= amount;
+        data += amount;
+    }
+
+    ca_context* ctx = ca_context_get_default();
+    if (!ctx) {
+        return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    /* No audio data? well, at least the WAV was valid. */
-    if (!audio_len)
-        return NS_OK;
-
-#if 0
-    printf("f: %d | c: %d | sps: %li | abps: %li | ba: %d | bps: %d | rate: %li\n",
-         format, channels, samples_per_sec, avg_bytes_per_sec, block_align, bits_per_sample, rate);
-#endif
-
-    /* open up connection to esd */  
-    EsdPlayStreamType EsdPlayStream = 
-        (EsdPlayStreamType) PR_FindFunctionSymbol(elib, 
-                                                  "esd_play_stream");
-    if (!EsdPlayStream)
-        return NS_ERROR_FAILURE;
-
-    mask = ESD_PLAY | ESD_STREAM;
-
-    if (bits_per_sample == 8)
-        mask |= ESD_BITS8;
-    else 
-        mask |= ESD_BITS16;
-
-    if (channels == 1)
-        mask |= ESD_MONO;
-    else 
-        mask |= ESD_STEREO;
-
-    nsAutoArrayPtr<PRUint8> buf;
+    ca_proplist *p;
+    ca_proplist_create(&p);
+    if (!p) {
+        return NS_ERROR_OUT_OF_MEMORY;
+    }
 
-    // ESD only handle little-endian data. 
-    // Swap the byte order if we're on a big-endian architecture.
-#ifdef IS_BIG_ENDIAN
-    if (bits_per_sample != 8) {
-        buf = new PRUint8[audio_len];
-        if (!buf)
-            return NS_ERROR_OUT_OF_MEMORY;
-        for (PRUint32 j = 0; j + 2 < audio_len; j += 2) {
-            buf[j]     = audio[j + 1];
-            buf[j + 1] = audio[j];
-        }
-
-        audio = buf;
+    nsCAutoString path;
+    rv = canberraFile->GetNativePath(path);
+    if (NS_FAILED(rv)) {
+        return rv;
     }
-#endif
 
-    fd = (*EsdPlayStream)(mask, samples_per_sec, NULL, "mozillaSound"); 
-  
-    if (fd < 0) {
-      int *esd_audio_format = (int *) PR_FindSymbol(elib, "esd_audio_format");
-      int *esd_audio_rate = (int *) PR_FindSymbol(elib, "esd_audio_rate");
-      EsdAudioOpenType EsdAudioOpen = (EsdAudioOpenType) PR_FindFunctionSymbol(elib, "esd_audio_open");
-      EsdAudioWriteType EsdAudioWrite = (EsdAudioWriteType) PR_FindFunctionSymbol(elib, "esd_audio_write");
-      EsdAudioCloseType EsdAudioClose = (EsdAudioCloseType) PR_FindFunctionSymbol(elib, "esd_audio_close");
-
-      if (!esd_audio_format || !esd_audio_rate ||
-          !EsdAudioOpen || !EsdAudioWrite || !EsdAudioClose)
-          return NS_ERROR_FAILURE;
-
-      *esd_audio_format = mask;
-      *esd_audio_rate = samples_per_sec;
-      fd = (*EsdAudioOpen)();
-
-      if (fd < 0)
-        return NS_ERROR_FAILURE;
-
-      (*EsdAudioWrite)(audio, audio_len);
-      (*EsdAudioClose)();
-    } else {
-      while (audio_len > 0) {
-        ssize_t written = write(fd, audio, audio_len);
-        if (written <= 0)
-          break;
-        audio += written;
-        audio_len -= written;
-      }
-      close(fd);
+    ca_proplist_sets(p, "media.filename", path.get());
+    if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) {
+        // Don't delete the temporary file here if ca_context_play_full succeeds
+        canberraFile.forget();
     }
+    ca_proplist_destroy(p);
 
     return NS_OK;
 }
 
 NS_METHOD nsSound::Beep()
 {
     ::gdk_beep();
     return NS_OK;
 }
 
 NS_METHOD nsSound::Play(nsIURL *aURL)
 {
-    nsresult rv;
-
     if (!mInited)
         Init();
 
-    if (!elib) 
-	    return NS_ERROR_NOT_AVAILABLE;
+    if (!libcanberra)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    bool isFile;
+    nsresult rv = aURL->SchemeIs("file", &isFile);
+    if (NS_SUCCEEDED(rv) && isFile) {
+        ca_context* ctx = ca_context_get_default();
+        if (!ctx) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
 
-    nsCOMPtr<nsIStreamLoader> loader;
-    rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
+        nsCAutoString path;
+        rv = aURL->GetPath(path);
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+
+        ca_context_play(ctx, 0, "media.filename", path.get(), NULL);
+    } else {
+        nsCOMPtr<nsIStreamLoader> loader;
+        rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
+    }
 
     return rv;
 }
 
 NS_IMETHODIMP nsSound::PlayEventSound(PRUint32 aEventId)
 {
     if (!mInited)
         Init();
 
     if (!libcanberra)
         return NS_OK;
 
     // Do we even want alert sounds?
-    // If so, what sound theme are we using?
     GtkSettings* settings = gtk_settings_get_default();
-    gchar* sound_theme_name = nsnull;
 
-    if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), "gtk-sound-theme-name") &&
-        g_object_class_find_property(G_OBJECT_GET_CLASS(settings), "gtk-enable-event-sounds")) {
+    if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+                                     "gtk-enable-event-sounds")) {
         gboolean enable_sounds = TRUE;
-        g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds,
-                               "gtk-sound-theme-name", &sound_theme_name,
-                               NULL);
+        g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, NULL);
 
         if (!enable_sounds) {
-            g_free(sound_theme_name);
             return NS_OK;
         }
     }
 
-    // This allows us to avoid race conditions with freeing the context by handing that
-    // responsibility to Glib, and still use one context at a time
-    ca_context* ctx = nsnull;
-    static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT;
-    ctx = (ca_context*) g_static_private_get(&ctx_static_private);
+    ca_context* ctx = ca_context_get_default();
     if (!ctx) {
-        ca_context_create(&ctx);
-        if (!ctx) {
-            g_free(sound_theme_name);
-            return NS_ERROR_OUT_OF_MEMORY;
-        }
-
-        g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy);
-    }
-
-    if (sound_theme_name) {
-        ca_context_change_props(ctx, "canberra.xdg-theme.name", sound_theme_name, NULL);
-        g_free(sound_theme_name);
+        return NS_ERROR_OUT_OF_MEMORY;
     }
 
     switch (aEventId) {
         case EVENT_ALERT_DIALOG_OPEN:
             ca_context_play(ctx, 0, "event.id", "dialog-warning", NULL);
             break;
         case EVENT_CONFIRM_DIALOG_OPEN:
             ca_context_play(ctx, 0, "event.id", "dialog-question", NULL);