Back to index

lightning-sunbird  0.9+nobinonly
nsSound.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:expandtab:shiftwidth=4:tabstop=4:
00003  */
00004 /* ***** BEGIN LICENSE BLOCK *****
00005  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00006  *
00007  * The contents of this file are subject to the Mozilla Public License Version
00008  * 1.1 (the "License"); you may not use this file except in compliance with
00009  * the License. You may obtain a copy of the License at
00010  * http://www.mozilla.org/MPL/
00011  *
00012  * Software distributed under the License is distributed on an "AS IS" basis,
00013  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00014  * for the specific language governing rights and limitations under the
00015  * License.
00016  *
00017  * The Original Code is mozilla.org code.
00018  *
00019  * The Initial Developer of the Original Code is
00020  * Netscape Communications Corporation.
00021  * Portions created by the Initial Developer are Copyright (C) 2000
00022  * the Initial Developer. All Rights Reserved.
00023  *
00024  * Contributor(s):
00025  *   Stuart Parmenter <pavlov@netscape.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include <string.h>
00042 
00043 #include "nscore.h"
00044 #include "plstr.h"
00045 #include "prlink.h"
00046 
00047 #include "nsSound.h"
00048 
00049 #include "nsIURL.h"
00050 #include "nsIFileURL.h"
00051 #include "nsNetUtil.h"
00052 #include "nsCOMPtr.h"
00053 #include "nsAutoPtr.h"
00054 
00055 #include <stdio.h>
00056 #include <unistd.h>
00057 
00058 #include <gtk/gtk.h>
00059 /* used with esd_open_sound */
00060 static int esdref = -1;
00061 static PRLibrary *elib = nsnull;
00062 
00063 // the following from esd.h
00064 
00065 #define ESD_BITS8  (0x0000)
00066 #define ESD_BITS16 (0x0001) 
00067 #define ESD_MONO (0x0010)
00068 #define ESD_STEREO (0x0020) 
00069 #define ESD_STREAM (0x0000)
00070 #define ESD_PLAY (0x1000)
00071 
00072 #define WAV_MIN_LENGTH 44
00073 
00074 typedef int (PR_CALLBACK *EsdOpenSoundType)(const char *host);
00075 typedef int (PR_CALLBACK *EsdCloseType)(int);
00076 
00077 /* used to play the sounds from the find symbol call */
00078 typedef int (PR_CALLBACK *EsdPlayStreamType)  (int, 
00079                                                int, 
00080                                                const char *, 
00081                                                const char *);
00082 typedef int  (PR_CALLBACK *EsdAudioOpenType)  (void);
00083 typedef int  (PR_CALLBACK *EsdAudioWriteType) (const void *, int);
00084 typedef void (PR_CALLBACK *EsdAudioCloseType) (void);
00085 
00086 NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
00087 
00088 
00089 nsSound::nsSound()
00090 {
00091     mInited = PR_FALSE;
00092 }
00093 
00094 nsSound::~nsSound()
00095 {
00096     /* see above comment */
00097     if (esdref != -1) {
00098         EsdCloseType EsdClose = (EsdCloseType) PR_FindSymbol(elib, "esd_close");
00099         (*EsdClose)(esdref);
00100         esdref = -1;
00101     }
00102 }
00103 
00104 NS_IMETHODIMP
00105 nsSound::Init()
00106 {
00107     /* we don't need to do esd_open_sound if we are only going to play files
00108        but we will if we want to do things like streams, etc
00109     */
00110     if (mInited) 
00111         return NS_OK;
00112     if (elib) 
00113         return NS_OK;
00114 
00115     EsdOpenSoundType EsdOpenSound;
00116 
00117     elib = PR_LoadLibrary("libesd.so.0");
00118     if (!elib) return NS_ERROR_FAILURE;
00119 
00120     EsdOpenSound = (EsdOpenSoundType) PR_FindSymbol(elib, "esd_open_sound");
00121 
00122     if (!EsdOpenSound)
00123         return NS_ERROR_FAILURE;
00124 
00125     esdref = (*EsdOpenSound)("localhost");
00126 
00127     if (!esdref)
00128         return NS_ERROR_FAILURE;
00129 
00130     mInited = PR_TRUE;
00131 
00132     return NS_OK;
00133 }
00134 
00135 #define GET_WORD(s, i) (s[i+1] << 8) | s[i]
00136 #define GET_DWORD(s, i) (s[i+3] << 24) | (s[i+2] << 16) | (s[i+1] << 8) | s[i]
00137 
00138 NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
00139                                         nsISupports *context,
00140                                         nsresult aStatus,
00141                                         PRUint32 dataLen,
00142                                         const PRUint8 *data)
00143 {
00144 
00145     // print a load error on bad status, and return
00146     if (NS_FAILED(aStatus)) {
00147 #ifdef DEBUG
00148         if (aLoader) {
00149             nsCOMPtr<nsIRequest> request;
00150             aLoader->GetRequest(getter_AddRefs(request));
00151             if (request) {
00152                 nsCOMPtr<nsIURI> uri;
00153                 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
00154                 if (channel) {
00155                       channel->GetURI(getter_AddRefs(uri));
00156                       if (uri) {
00157                             nsCAutoString uriSpec;
00158                             uri->GetSpec(uriSpec);
00159                             printf("Failed to load %s\n", uriSpec.get());
00160                       }
00161                 }
00162             }
00163         }
00164 #endif
00165         return aStatus;
00166     }
00167 
00168     int fd, mask = 0;
00169     PRUint32 samples_per_sec = 0, avg_bytes_per_sec = 0, chunk_len = 0;
00170     PRUint16 format, channels = 1, bits_per_sample = 0;
00171     const PRUint8 *audio = nsnull;
00172     size_t audio_len = 0;
00173 
00174     if (memcmp(data, "RIFF", 4)) {
00175 #ifdef DEBUG
00176         printf("We only support WAV files currently.\n");
00177 #endif
00178         return NS_ERROR_FAILURE;
00179     }
00180 
00181     if (dataLen <= WAV_MIN_LENGTH) {
00182         NS_WARNING("WAV files should be longer than 44 bytes.");
00183         return NS_ERROR_FAILURE;
00184     }
00185 
00186     PRUint32 i = 12;
00187     while (i + 7 < dataLen) {
00188         if (!memcmp(data + i, "fmt ", 4) && !chunk_len) {
00189             i += 4;
00190 
00191             /* length of the rest of this subblock (should be 16 for PCM data */
00192             chunk_len = GET_DWORD(data, i);
00193             i += 4;
00194 
00195             if (chunk_len < 16 || i + chunk_len >= dataLen) {
00196                 NS_WARNING("Invalid WAV file: bad fmt chunk.");
00197                 return NS_ERROR_FAILURE;
00198             }
00199 
00200             format = GET_WORD(data, i);
00201             i += 2;
00202 
00203             channels = GET_WORD(data, i);
00204             i += 2;
00205 
00206             samples_per_sec = GET_DWORD(data, i);
00207             i += 4;
00208 
00209             avg_bytes_per_sec = GET_DWORD(data, i);
00210             i += 4;
00211 
00212             // block align
00213             i += 2;
00214 
00215             bits_per_sample = GET_WORD(data, i);
00216             i += 2;
00217 
00218             /* we don't support WAVs with odd compression codes */
00219             if (chunk_len != 16)
00220                 NS_WARNING("Extra format bits found in WAV. Ignoring");
00221 
00222             i += chunk_len - 16;
00223         } else if (!memcmp(data + i, "data", 4)) {
00224             i += 4;
00225             if (!chunk_len) {
00226                 NS_WARNING("Invalid WAV file: no fmt chunk found");
00227                 return NS_ERROR_FAILURE;
00228             }
00229 
00230             audio_len = GET_DWORD(data, i);
00231             i += 4;
00232 
00233             /* try to play truncated WAVs */
00234             if (i + audio_len > dataLen)
00235                 audio_len = dataLen - i;
00236 
00237             audio = data + i;
00238             break;
00239         } else {
00240             i += 4;
00241             i += GET_DWORD(data, i);
00242             i += 4;
00243         }
00244     }
00245 
00246     if (!audio) {
00247         NS_WARNING("Invalid WAV file: no data chunk found");
00248         return NS_ERROR_FAILURE;
00249     }
00250 
00251     /* No audio data? well, at least the WAV was valid. */
00252     if (!audio_len)
00253         return NS_OK;
00254 
00255 #if 0
00256     printf("f: %d | c: %d | sps: %li | abps: %li | ba: %d | bps: %d | rate: %li\n",
00257          format, channels, samples_per_sec, avg_bytes_per_sec, block_align, bits_per_sample, rate);
00258 #endif
00259 
00260     /* open up connection to esd */  
00261     EsdPlayStreamType EsdPlayStream = 
00262         (EsdPlayStreamType) PR_FindSymbol(elib, 
00263                                           "esd_play_stream");
00264     // XXX what if that fails? (Bug 241738)
00265 
00266     mask = ESD_PLAY | ESD_STREAM;
00267 
00268     if (bits_per_sample == 8)
00269         mask |= ESD_BITS8;
00270     else 
00271         mask |= ESD_BITS16;
00272 
00273     if (channels == 1)
00274         mask |= ESD_MONO;
00275     else 
00276         mask |= ESD_STEREO;
00277 
00278     nsAutoArrayPtr<PRUint8> buf;
00279 
00280     // ESD only handle little-endian data. 
00281     // Swap the byte order if we're on a big-endian architecture.
00282 #ifdef IS_BIG_ENDIAN
00283     if (bits_per_sample != 8) {
00284         buf = new PRUint8[audio_len];
00285         if (!buf)
00286             return NS_ERROR_OUT_OF_MEMORY;
00287         for (PRUint32 j = 0; j + 2 < audio_len; j += 2) {
00288             buf[j]     = audio[j + 1];
00289             buf[j + 1] = audio[j];
00290         }
00291 
00292        audio = buf;
00293     }
00294 #endif
00295 
00296     fd = (*EsdPlayStream)(mask, samples_per_sec, NULL, "mozillaSound"); 
00297   
00298     if (fd < 0) {
00299       int *esd_audio_format = (int *) PR_FindSymbol(elib, "esd_audio_format");
00300       int *esd_audio_rate = (int *) PR_FindSymbol(elib, "esd_audio_rate");
00301       EsdAudioOpenType EsdAudioOpen = (EsdAudioOpenType) PR_FindSymbol(elib, "esd_audio_open");
00302       EsdAudioWriteType EsdAudioWrite = (EsdAudioWriteType) PR_FindSymbol(elib, "esd_audio_write");
00303       EsdAudioCloseType EsdAudioClose = (EsdAudioCloseType) PR_FindSymbol(elib, "esd_audio_close");
00304 
00305       *esd_audio_format = mask;
00306       *esd_audio_rate = samples_per_sec;
00307       fd = (*EsdAudioOpen)();
00308 
00309       if (fd < 0)
00310         return NS_ERROR_FAILURE;
00311 
00312       (*EsdAudioWrite)(audio, audio_len);
00313       (*EsdAudioClose)();
00314     } else {
00315       while (audio_len > 0) {
00316         size_t written = write(fd, audio, audio_len);
00317         if (written <= 0)
00318           break;
00319         audio += written;
00320         audio_len -= written;
00321       }
00322       close(fd);
00323     }
00324 
00325     return NS_OK;
00326 }
00327 
00328 NS_METHOD nsSound::Beep()
00329 {
00330     ::gdk_beep();
00331     return NS_OK;
00332 }
00333 
00334 NS_METHOD nsSound::Play(nsIURL *aURL)
00335 {
00336     nsresult rv;
00337 
00338     if (!mInited)
00339         Init();
00340 
00341     if (!elib) 
00342            return NS_ERROR_FAILURE;
00343 
00344     nsCOMPtr<nsIStreamLoader> loader;
00345     rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
00346 
00347     return rv;
00348 }
00349 
00350 NS_IMETHODIMP nsSound::PlaySystemSound(const char *aSoundAlias)
00351 {
00352     if (!aSoundAlias)
00353         return NS_ERROR_FAILURE;
00354 
00355     if (strcmp(aSoundAlias, "_moz_mailbeep") == 0) {
00356         return Beep();
00357     }
00358 
00359     nsresult rv;
00360     nsCOMPtr <nsIURI> fileURI;
00361 
00362     // create a nsILocalFile and then a nsIFileURL from that
00363     nsCOMPtr <nsILocalFile> soundFile;
00364     rv = NS_NewNativeLocalFile(nsDependentCString(aSoundAlias), PR_TRUE, 
00365                                getter_AddRefs(soundFile));
00366     NS_ENSURE_SUCCESS(rv,rv);
00367 
00368     rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile);
00369     NS_ENSURE_SUCCESS(rv,rv);
00370 
00371     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv);
00372     NS_ENSURE_SUCCESS(rv,rv);
00373 
00374     rv = Play(fileURL);
00375 
00376     return rv;
00377 }