gecko/widget/src/os2/nsSound.cpp

514 lines
17 KiB
C++
Raw Normal View History

/* vim: set sw=2 sts=2 et cin: */
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Stuart Parmenter <pavlov@netscape.com>
* John Fairhurst <john_fairhurst@iname.com>
* IBM Corp.
* Peter Weilbacher <mozilla@Weilbacher.org>
* Lars Erdmann
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nscore.h"
#include "plstr.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_MMIOOS2
#include <os2.h>
#include <mmioos2.h>
#include <mcios2.h>
#define MCI_ERROR_LENGTH 128
#include "nsSound.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsDirectoryServiceDefs.h"
#include "nsNativeCharsetUtils.h"
NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
static int sInitialized = 0;
static PRBool sMMPMInstalled = PR_FALSE;
static HMODULE sHModMMIO = NULLHANDLE;
static HMODULE sHModMDM = NULLHANDLE;
// function pointer definitions, include underscore (work around redef. warning)
HMMIO (*APIENTRY _mmioOpen)(PSZ, PMMIOINFO, ULONG);
USHORT (*APIENTRY _mmioClose)(HMMIO, USHORT);
ULONG (*APIENTRY _mmioGetFormats)(PMMFORMATINFO, LONG, PVOID, PLONG, ULONG, ULONG);
ULONG (*APIENTRY _mciSendCommand)(USHORT, USHORT, ULONG, PVOID, USHORT);
#ifdef DEBUG
ULONG (*APIENTRY _mmioGetLastError)(HMMIO);
ULONG (*APIENTRY _mmioQueryFormatCount)(PMMFORMATINFO, PLONG, ULONG, ULONG);
ULONG (*APIENTRY _mmioGetFormatName)(PMMFORMATINFO, PSZ, PLONG, ULONG, ULONG);
ULONG (*APIENTRY _mciGetErrorString)(ULONG, PSZ, USHORT);
#endif
ULONG (*APIENTRY _mmioIniFileHandler)(PMMINIFILEINFO, ULONG);
// argument structure to pass to the background thread
typedef struct _ARGBUFFER
{
HEV hev;
PRUint32 bufLen;
const char *buffer;
PSZ pszFilename;
} ARGBUFFER;
////////////////////////////////////////////////////////////////////////
static void InitGlobals(void)
{
ULONG ulrc = 0;
char LoadError[CCHMAXPATH];
ulrc = DosLoadModule(LoadError, CCHMAXPATH, "MMIO", &sHModMMIO);
ulrc += DosLoadModule(LoadError, CCHMAXPATH, "MDM", &sHModMDM);
if (ulrc == NO_ERROR) {
#ifdef DEBUG
printf("InitGlobals: MMOS2 is installed, both DLLs loaded\n");
#endif
sMMPMInstalled = PR_TRUE;
// MMOS2 is installed, so we can query the necessary functions
// mmio functions are in MMIO.DLL
ulrc = DosQueryProcAddr(sHModMMIO, 0L, "mmioOpen", (PFN *)&_mmioOpen);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioClose", (PFN *)&_mmioClose);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioGetFormats", (PFN *)&_mmioGetFormats);
// mci functions are in MDM.DLL
ulrc += DosQueryProcAddr(sHModMDM, 0L, "mciSendCommand", (PFN *)&_mciSendCommand);
#ifdef DEBUG
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioGetLastError", (PFN *)&_mmioGetLastError);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioQueryFormatCount", (PFN *)&_mmioQueryFormatCount);
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioGetFormatName", (PFN *)&_mmioGetFormatName);
ulrc += DosQueryProcAddr(sHModMDM, 0L, "mciGetErrorString", (PFN *)&_mciGetErrorString);
#endif
ulrc += DosQueryProcAddr(sHModMMIO, 0L, "mmioIniFileHandler", (PFN *)&_mmioIniFileHandler);
// if one of these failed, we have some kind of non-functional MMOS2 installation
if (ulrc != NO_ERROR) {
NS_WARNING("MMOS2 is installed, but seems to have corrupt DLLs");
sMMPMInstalled = PR_FALSE;
}
}
}
////////////////////////////////////////////////////////////////////////
// Tries to determine the data format in the buffer using file "magic"
// and a loop through MMOS2 audio codecs.
// Returns the FourCC handle for the format, or 0 when failing to find format
// and codec.
FOURCC determineFourCC(PRUint32 aDataLen, const char *aData)
{
FOURCC fcc = 0;
// Start to compare the first bytes of the data with magic to determine the
// most likely format upfront.
if (memcmp(aData, "RIFF", 4) == 0) { // WAV
fcc = mmioFOURCC('W', 'A', 'V', 'E');
} else if (memcmp(aData, "ID3", 3) == 0 || // likely MP3 with ID3 header
((aData[0] & 0xFF) == 0xFF && // various versions of MPEG layer 3
((aData[1] & 0xFE) == 0xFA || // v1
(aData[1] & 0xFE) == 0xF2 || // v2
(aData[1] & 0xFE) == 0xE2))) // v2.5
{
fcc = mmioFOURCC('M','P','3',' ');
} else if (memcmp(aData, "OggS", 4) == 0) { // OGG
fcc = mmioFOURCC('O','G','G','S');
} else if (memcmp(aData, "fLaC", 4) == 0) { // FLAC
fcc = mmioFOURCC('f','L','a','C');
}
// The following is too flakey because several OS/2 IOProc don't behave as
// they should and would cause us to crash. So just skip this for now...
#if 0
if (fcc) // already found one
return fcc;
// None of the popular formats found, so use the list of MMOS2 audio codecs to
// find one that can open the file.
MMFORMATINFO mmfi;
LONG lNum;
memset(&mmfi, '\0', sizeof(mmfi));
mmfi.ulStructLen = sizeof(mmfi);
mmfi.ulMediaType |= MMIO_MEDIATYPE_AUDIO;
ULONG ulrc = _mmioQueryFormatCount(&mmfi, &lNum, 0L, 0L);
PMMFORMATINFO mmflist = (PMMFORMATINFO)calloc(lNum, sizeof(MMFORMATINFO));
LONG lFormats;
ulrc = _mmioGetFormats(&mmfi, lNum, mmflist, &lFormats, 0L, 0L);
MMIOINFO mi;
memset(&mi, '\0', sizeof(mi));
mi.fccChildIOProc = FOURCC_MEM;
unsigned char szBuffer[sizeof(FOURCC) + CCHMAXPATH + 4];
for (int i = lFormats-1; i >= 0; i--) {
// Loop through formats. Do it backwards to find at least WAV before the
// faulty VORBIS/FLAC/MP3 IOProcs that will open any format.
MMFORMATINFO mmfi = mmflist[i];
#ifdef DEBUG
LONG lBytesRead;
_mmioGetFormatName(&mmfi, (char *)szBuffer, &lBytesRead, 0L, 0L);
printf("determineFour Codec %d: name=%s media=0x%lx ext=%s fcc=%c%c%c%c/%ld/%p\n",
i, szBuffer, mmfi.ulMediaType, mmfi.szDefaultFormatExt,
(char)(mmfi.fccIOProc), (char)(mmfi.fccIOProc >> 8),
(char)(mmfi.fccIOProc >> 16), (char)(mmfi.fccIOProc >> 24),
mmfi.fccIOProc, (void *)mmfi.fccIOProc);
#endif
// this codec likely crashes the program when the buffer is not of the
// expected format
if (mmfi.fccIOProc == mmioFOURCC('A','V','C','A')) {
continue;
}
mi.fccIOProc = mmfi.fccIOProc;
HMMIO hmmio= _mmioOpen(NULL, &mi, MMIO_READ);
if (hmmio) {
fcc = mmfi.fccIOProc;
_mmioClose(hmmio, 0);
break;
}
}
free(mmflist);
#endif
#ifdef DEBUG
printf("determineFourCC: Codec fcc is 0x%lx or --%c%c%c%c--\n", fcc,
(char)(fcc), (char)(fcc >> 8), (char)(fcc >> 16), (char)(fcc >> 24));
#endif
return fcc;
}
// Play the sound that was set up in the argument structure. If an error occurs,
// beep at least. To be used as function for a new background thread.
static void playSound(void *aArgs)
{
ULONG ulrc = NO_ERROR;
ARGBUFFER args;
memcpy(&args, aArgs, sizeof(args));
MMIOINFO mi;
memset(&mi, '\0', sizeof(mi));
HMMIO hmmio = NULLHANDLE;
do { // inner block (break in case of error)
if (args.pszFilename) {
// determine size of file that we want to read
FILESTATUS3 fs3;
memset(&fs3, '\0', sizeof(fs3));
ulrc = DosQueryPathInfo(args.pszFilename, FIL_STANDARD, &fs3, sizeof(fs3));
mi.cchBuffer = fs3.cbFile;
} else {
// use size of the existing buffer
mi.cchBuffer = args.bufLen;
}
// Read or copy the sound into a local memory buffer for easy playback.
// (If we got the sound in a buffer originally, that buffer could
// "disappear" while we are still playing it.)
ulrc = DosAllocMem((PPVOID)&mi.pchBuffer, mi.cchBuffer,
#ifdef OS2_HIGH_MEMORY /* only if compiled with high-memory */
OBJ_ANY | /* support, we can allocate anywhere! */
#endif
PAG_READ | PAG_WRITE | PAG_COMMIT);
if (ulrc != NO_ERROR) {
#ifdef DEBUG
printf("playSound: Could not allocate the sound buffer, ulrc=%ld\n", ulrc);
#endif
break;
}
if (args.pszFilename) {
// read the sound from file into memory
HFILE hf = NULLHANDLE;
ULONG ulAction = 0;
ulrc = DosOpen(args.pszFilename, &hf, &ulAction, 0, FILE_NORMAL,
OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE,
NULL);
if (ulrc != NO_ERROR) {
#ifdef DEBUG
printf("playSound: could not open the sound file \"%s\" (%ld)\n",
args.pszFilename, ulrc);
#endif
break;
}
ULONG ulRead = 0;
ulrc = DosRead(hf, mi.pchBuffer, mi.cchBuffer, &ulRead);
DosClose(hf);
if (ulrc != NO_ERROR) {
#ifdef DEBUG
printf("playSound: read %ld of %ld bytes from the sound file \"%s\" (%ld)\n",
ulRead, mi.cchBuffer, args.pszFilename, ulrc);
#endif
break;
}
} else {
// copy the passed sound buffer into local memory
memcpy(mi.pchBuffer, args.buffer, args.bufLen);
}
DosPostEventSem(args.hev); // calling thread can continue
// Now the sound is loaded into memory in any case, play it from there.
mi.fccChildIOProc = FOURCC_MEM;
mi.fccIOProc = determineFourCC(mi.cchBuffer, mi.pchBuffer);
if (!mi.fccIOProc) {
NS_WARNING("playSound: unknown sound format in memory buffer");
break;
}
mi.ulTranslate = MMIO_TRANSLATEDATA | MMIO_TRANSLATEHEADER;
hmmio = _mmioOpen(NULL, &mi, MMIO_READ | MMIO_DENYWRITE);
if (!hmmio) {
#ifdef DEBUG
ULONG ulrc = _mmioGetLastError(hmmio);
if (args.pszFilename) {
printf("playSound: mmioOpen failed, cannot play sound from \"%s\" (%ld)\n",
args.pszFilename, ulrc);
} else {
printf("playSound: mmioOpen failed, cannot play sound buffer (%ld)\n",
ulrc);
}
#endif
break;
}
// open the sound device
MCI_OPEN_PARMS mop;
memset(&mop, '\0', sizeof(mop));
mop.pszElementName = (PSZ)hmmio;
mop.pszDeviceType = (PSZ)MAKEULONG(MCI_DEVTYPE_WAVEFORM_AUDIO, 0);
ulrc = _mciSendCommand(0, MCI_OPEN,
MCI_OPEN_MMIO | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE | MCI_WAIT,
(PVOID)&mop, 0);
if (ulrc != MCIERR_SUCCESS) {
#ifdef DEBUG
CHAR errorBuffer[MCI_ERROR_LENGTH];
_mciGetErrorString(ulrc, errorBuffer, MCI_ERROR_LENGTH);
printf("playSound: mciSendCommand with MCI_OPEN_MMIO returned %ld: %s\n",
ulrc, errorBuffer);
#endif
break;
}
// play the sound
MCI_PLAY_PARMS mpp;
memset(&mpp, '\0', sizeof(mpp));
ulrc = _mciSendCommand(mop.usDeviceID, MCI_PLAY, MCI_WAIT, &mpp, 0);
#ifdef DEBUG
// just ignore further failures in non-debug mode
if (ulrc != MCIERR_SUCCESS) {
CHAR errorBuffer[MCI_ERROR_LENGTH];
_mciGetErrorString(ulrc, errorBuffer, MCI_ERROR_LENGTH);
printf("playSound: mciSendCommand with MCI_PLAY returned %ld: %s\n",
ulrc, errorBuffer);
}
#endif
// end playing
ulrc = _mciSendCommand(mop.usDeviceID, MCI_STOP, MCI_WAIT, &mpp, 0); // be nice
ulrc = _mciSendCommand(mop.usDeviceID, MCI_CLOSE, MCI_WAIT, &mpp, 0);
#ifdef DEBUG
if (ulrc != MCIERR_SUCCESS) {
CHAR errorBuffer[MCI_ERROR_LENGTH];
_mciGetErrorString(ulrc, errorBuffer, MCI_ERROR_LENGTH);
printf("playSound: mciSendCommand with MCI_CLOSE returned %ld: %s\n",
ulrc, errorBuffer);
}
#endif
_mmioClose(hmmio, 0);
DosFreeMem(mi.pchBuffer);
_endthread();
} while(0); // end of inner block
// cleanup after an error
WinAlarm(HWND_DESKTOP, WA_WARNING); // Beep()
if (hmmio)
_mmioClose(hmmio, 0);
if (mi.pchBuffer)
DosFreeMem(mi.pchBuffer);
}
////////////////////////////////////////////////////////////////////////
nsSound::nsSound()
{
if (!sInitialized) {
InitGlobals();
}
sInitialized++;
#ifdef DEBUG
printf("nsSound::nsSound: sInitialized=%d\n", sInitialized);
#endif
}
nsSound::~nsSound()
{
sInitialized--;
#ifdef DEBUG
printf("nsSound::~nsSound: sInitialized=%d\n", sInitialized);
#endif
// (try to) unload modules after last user ended
if (!sInitialized) {
#ifdef DEBUG
printf("nsSound::~nsSound: Trying to free modules...\n");
#endif
ULONG ulrc;
ulrc = DosFreeModule(sHModMMIO);
ulrc += DosFreeModule(sHModMDM);
if (ulrc != NO_ERROR) {
NS_WARNING("DosFreeModule did not work");
}
}
}
NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
nsISupports *context,
nsresult aStatus,
PRUint32 dataLen,
const PRUint8 *data)
{
if (NS_FAILED(aStatus)) {
#ifdef DEBUG
if (aLoader) {
nsCOMPtr<nsIRequest> request;
aLoader->GetRequest(getter_AddRefs(request));
if (request) {
nsCOMPtr<nsIURI> uri;
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
if (channel) {
channel->GetURI(getter_AddRefs(uri));
if (uri) {
nsCAutoString uriSpec;
uri->GetSpec(uriSpec);
printf("Failed to load %s\n", uriSpec.get());
}
}
}
}
#endif
return NS_ERROR_FAILURE;
}
if (!sMMPMInstalled) {
NS_WARNING("Sound output only works with MMOS2 installed");
Beep();
return NS_OK;
}
ARGBUFFER arg;
memset(&arg, '\0', sizeof(arg));
APIRET rc = DosCreateEventSem(NULL, &(arg.hev), 0UL, 0UL);
// Play the sound on a new thread using MMOS2, in this case pass
// the memory buffer in the argument structure.
arg.bufLen = dataLen;
arg.buffer = (char *)data;
_beginthread(playSound, NULL, 32768, (void *)&arg);
// Wait until the buffer was copied, but not indefinitely to not block the
// UI in case a really large sound file is copied.
rc = DosWaitEventSem(arg.hev, 100);
rc = DosCloseEventSem(arg.hev);
return NS_OK;
}
NS_METHOD nsSound::Beep()
{
WinAlarm(HWND_DESKTOP, WA_WARNING);
return NS_OK;
}
NS_METHOD nsSound::Play(nsIURL *aURL)
{
nsresult rv;
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
return rv;
}
NS_IMETHODIMP nsSound::Init()
{
return NS_OK;
}
NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
{
// Just beep if MMPM isn't installed.
if (!sMMPMInstalled) {
Beep();
return NS_OK;
}
if (NS_IsMozAliasSound(aSoundAlias)) {
// We don't have a default mail sound on OS/2, so just beep.
if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
Beep();
return NS_OK;
}
nsCAutoString nativeSoundAlias;
NS_CopyUnicodeToNative(aSoundAlias, nativeSoundAlias);
ARGBUFFER arg;
memset(&arg, '\0', sizeof(arg));
APIRET rc = DosCreateEventSem(NULL, &(arg.hev), 0UL, 0UL);
// Play the sound on a new thread using MMOS2, in this case pass
// the filename in the argument structure.
arg.pszFilename = (PSZ)nativeSoundAlias.get();
_beginthread(playSound, NULL, 32768, (void *)&arg);
// Try to wait a while until the file is loaded, but not too long...
rc = DosWaitEventSem(arg.hev, 100);
rc = DosCloseEventSem(arg.hev);
return NS_OK;
}