Files
wxWidgets/src/unix/sound.cpp

703 lines
18 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/unix/sound.cpp
// Purpose: wxSound
// Author: Marcel Rasche, Vaclav Slavik
// Modified by:
// Created: 25/10/98
// RCS-ID: $Id$
// Copyright: (c) Julian Smart, Open Source Applications Foundation
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#if defined(__BORLANDC__)
#pragma hdrstop
#endif
#if wxUSE_SOUND
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#ifdef HAVE_SYS_SOUNDCARD_H
#include <sys/soundcard.h>
#endif
#ifndef WX_PRECOMP
#include "wx/event.h"
#include "wx/intl.h"
#include "wx/log.h"
#endif
#include "wx/thread.h"
#include "wx/file.h"
#include "wx/module.h"
#include "wx/sound.h"
#include "wx/dynlib.h"
#if wxUSE_THREADS
// mutex for all wxSound's synchronization
static wxMutex gs_soundMutex;
#endif
// ----------------------------------------------------------------------------
// wxSoundData
// ----------------------------------------------------------------------------
void wxSoundData::IncRef()
{
#if wxUSE_THREADS
wxMutexLocker locker(gs_soundMutex);
#endif
m_refCnt++;
}
void wxSoundData::DecRef()
{
#if wxUSE_THREADS
wxMutexLocker locker(gs_soundMutex);
#endif
if (--m_refCnt == 0)
delete this;
}
wxSoundData::~wxSoundData()
{
delete[] m_dataWithHeader;
}
// ----------------------------------------------------------------------------
// wxSoundBackendNull, used in absence of audio API or card
// ----------------------------------------------------------------------------
class wxSoundBackendNull : public wxSoundBackend
{
public:
wxString GetName() const { return _("No sound"); }
int GetPriority() const { return 0; }
bool IsAvailable() const { return true; }
bool HasNativeAsyncPlayback() const { return true; }
bool Play(wxSoundData *WXUNUSED(data), unsigned WXUNUSED(flags),
volatile wxSoundPlaybackStatus *WXUNUSED(status))
{ return true; }
void Stop() {}
bool IsPlaying() const { return false; }
};
// ----------------------------------------------------------------------------
// wxSoundBackendOSS, for Linux
// ----------------------------------------------------------------------------
#ifdef HAVE_SYS_SOUNDCARD_H
#ifndef AUDIODEV
#define AUDIODEV "/dev/dsp" // Default path for audio device
#endif
class wxSoundBackendOSS : public wxSoundBackend
{
public:
wxString GetName() const { return _T("Open Sound System"); }
int GetPriority() const { return 10; }
bool IsAvailable() const;
bool HasNativeAsyncPlayback() const { return false; }
bool Play(wxSoundData *data, unsigned flags,
volatile wxSoundPlaybackStatus *status);
void Stop() {}
bool IsPlaying() const { return false; }
private:
int OpenDSP(const wxSoundData *data);
bool InitDSP(int dev, const wxSoundData *data);
int m_DSPblkSize; // Size of the DSP buffer
bool m_needConversion;
};
bool wxSoundBackendOSS::IsAvailable() const
{
int fd;
fd = open(AUDIODEV, O_WRONLY | O_NONBLOCK);
if (fd < 0)
return false;
close(fd);
return true;
}
bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags,
volatile wxSoundPlaybackStatus *status)
{
int dev = OpenDSP(data);
if (dev < 0)
return false;
ioctl(dev, SNDCTL_DSP_SYNC, 0);
do
{
bool play = true;
int i;
unsigned l = 0;
size_t datasize = data->m_dataBytes;
do
{
if (status->m_stopRequested)
{
wxLogTrace(_T("sound"), _T("playback stopped"));
close(dev);
return true;
}
i= (int)((l + m_DSPblkSize) < datasize ?
m_DSPblkSize : (datasize - l));
if (write(dev, &data->m_data[l], i) != i)
{
play = false;
}
l += i;
} while (play && l < datasize);
} while (flags & wxSOUND_LOOP);
close(dev);
return true;
}
int wxSoundBackendOSS::OpenDSP(const wxSoundData *data)
{
int dev = -1;
if ((dev = open(AUDIODEV, O_WRONLY, 0)) <0)
return -1;
if (!InitDSP(dev, data) || m_needConversion)
{
close(dev);
return -1;
}
return dev;
}
bool wxSoundBackendOSS::InitDSP(int dev, const wxSoundData *data)
{
unsigned tmp;
// Reset the dsp
if (ioctl(dev, SNDCTL_DSP_RESET, 0) < 0)
{
wxLogTrace(_T("sound"), _T("unable to reset dsp"));
return false;
}
m_needConversion = false;
tmp = data->m_bitsPerSample;
if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &tmp) < 0)
{
wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)"));
return false;
}
if (tmp != data->m_bitsPerSample)
{
wxLogTrace(_T("sound"),
_T("Unable to set DSP sample size to %d (wants %d)"),
data->m_bitsPerSample, tmp);
m_needConversion = true;
}
unsigned stereo = data->m_channels == 1 ? 0 : 1;
tmp = stereo;
if (ioctl(dev, SNDCTL_DSP_STEREO, &tmp) < 0)
{
wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_STEREO)"));
return false;
}
if (tmp != stereo)
{
wxLogTrace(_T("sound"), _T("Unable to set DSP to %s."), stereo? _T("stereo"):_T("mono"));
m_needConversion = true;
}
tmp = data->m_samplingRate;
if (ioctl(dev, SNDCTL_DSP_SPEED, &tmp) < 0)
{
wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SPEED)"));
return false;
}
if (tmp != data->m_samplingRate)
{
// If the rate the sound card is using is not within 1% of what the
// data specified then override the data setting. The only reason not
// to always override this is because of clock-rounding
// problems. Sound cards will sometimes use things like 44101 when you
// ask for 44100. No need overriding this and having strange output
// file rates for something that we can't hear anyways.
if (data->m_samplingRate - tmp > (tmp * .01) ||
tmp - data->m_samplingRate > (tmp * .01)) {
wxLogTrace(_T("sound"),
_T("Unable to set DSP sampling rate to %d (wants %d)"),
data->m_samplingRate, tmp);
m_needConversion = true;
}
}
// Do this last because some drivers can adjust the buffer sized based on
// the sampling rate, etc.
if (ioctl(dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize) < 0)
{
wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_GETBLKSIZE)"));
return false;
}
return true;
}
#endif // HAVE_SYS_SOUNDCARD_H
// ----------------------------------------------------------------------------
// wxSoundSyncOnlyAdaptor
// ----------------------------------------------------------------------------
#if wxUSE_THREADS
class wxSoundSyncOnlyAdaptor;
// this class manages asynchronous playback of audio if the backend doesn't
// support it natively (e.g. OSS backend)
class wxSoundAsyncPlaybackThread : public wxThread
{
public:
wxSoundAsyncPlaybackThread(wxSoundSyncOnlyAdaptor *adaptor,
wxSoundData *data, unsigned flags)
: wxThread(), m_adapt(adaptor), m_data(data), m_flags(flags) {}
virtual ExitCode Entry();
protected:
wxSoundSyncOnlyAdaptor *m_adapt;
wxSoundData *m_data;
unsigned m_flags;
};
#endif // wxUSE_THREADS
// This class turns wxSoundBackend that doesn't support asynchronous playback
// into one that does
class wxSoundSyncOnlyAdaptor : public wxSoundBackend
{
public:
wxSoundSyncOnlyAdaptor(wxSoundBackend *backend)
: m_backend(backend), m_playing(false) {}
~wxSoundSyncOnlyAdaptor()
{
delete m_backend;
}
wxString GetName() const
{
return m_backend->GetName();
}
int GetPriority() const
{
return m_backend->GetPriority();
}
bool IsAvailable() const
{
return m_backend->IsAvailable();
}
bool HasNativeAsyncPlayback() const
{
return true;
}
bool Play(wxSoundData *data, unsigned flags,
volatile wxSoundPlaybackStatus *status);
void Stop();
bool IsPlaying() const;
private:
friend class wxSoundAsyncPlaybackThread;
wxSoundBackend *m_backend;
bool m_playing;
#if wxUSE_THREADS
// player thread holds this mutex and releases it after it finishes
// playing, so that the main thread knows when it can play sound
wxMutex m_mutexRightToPlay;
wxSoundPlaybackStatus m_status;
#endif
};
#if wxUSE_THREADS
wxThread::ExitCode wxSoundAsyncPlaybackThread::Entry()
{
m_adapt->m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC,
&m_adapt->m_status);
m_data->DecRef();
m_adapt->m_playing = false;
m_adapt->m_mutexRightToPlay.Unlock();
wxLogTrace(_T("sound"), _T("terminated async playback thread"));
return 0;
}
#endif
bool wxSoundSyncOnlyAdaptor::Play(wxSoundData *data, unsigned flags,
volatile wxSoundPlaybackStatus *status)
{
Stop();
if (flags & wxSOUND_ASYNC)
{
#if wxUSE_THREADS
m_mutexRightToPlay.Lock();
m_status.m_playing = true;
m_status.m_stopRequested = false;
data->IncRef();
wxThread *th = new wxSoundAsyncPlaybackThread(this, data, flags);
th->Create();
th->Run();
wxLogTrace(_T("sound"), _T("launched async playback thread"));
return true;
#else
wxLogError(_("Unable to play sound asynchronously."));
return false;
#endif
}
else
{
#if wxUSE_THREADS
m_mutexRightToPlay.Lock();
#endif
bool rv = m_backend->Play(data, flags, status);
#if wxUSE_THREADS
m_mutexRightToPlay.Unlock();
#endif
return rv;
}
}
void wxSoundSyncOnlyAdaptor::Stop()
{
wxLogTrace(_T("sound"), _T("asking audio to stop"));
#if wxUSE_THREADS
// tell the player thread (if running) to stop playback ASAP:
m_status.m_stopRequested = true;
// acquire the mutex to be sure no sound is being played, then
// release it because we don't need it for anything (the effect of this
// is that calling thread will wait until playback thread reacts to
// our request to interrupt playback):
m_mutexRightToPlay.Lock();
m_mutexRightToPlay.Unlock();
wxLogTrace(_T("sound"), _T("audio was stopped"));
#endif
}
bool wxSoundSyncOnlyAdaptor::IsPlaying() const
{
#if wxUSE_THREADS
return m_status.m_playing;
#else
return false;
#endif
}
// ----------------------------------------------------------------------------
// wxSound
// ----------------------------------------------------------------------------
wxSoundBackend *wxSound::ms_backend = NULL;
// FIXME - temporary, until we have plugins architecture
#if wxUSE_LIBSDL
#if wxUSE_PLUGINS
wxDynamicLibrary *wxSound::ms_backendSDL = NULL;
#else
extern "C" wxSoundBackend *wxCreateSoundBackendSDL();
#endif
#endif
wxSound::wxSound() : m_data(NULL)
{
}
wxSound::wxSound(const wxString& sFileName, bool isResource) : m_data(NULL)
{
Create(sFileName, isResource);
}
wxSound::wxSound(int size, const wxByte* data) : m_data(NULL)
{
Create(size, data);
}
wxSound::~wxSound()
{
Free();
}
bool wxSound::Create(const wxString& fileName,
bool WXUNUSED_UNLESS_DEBUG(isResource))
{
wxASSERT_MSG( !isResource,
_T("Loading sound from resources is only supported on Windows") );
Free();
wxFile fileWave;
if (!fileWave.Open(fileName, wxFile::read))
{
return false;
}
wxFileOffset lenOrig = fileWave.Length();
if ( lenOrig == wxInvalidOffset )
return false;
size_t len = wx_truncate_cast(size_t, lenOrig);
wxUint8 *data = new wxUint8[len];
if ( fileWave.Read(data, len) != lenOrig )
{
delete [] data;
wxLogError(_("Couldn't load sound data from '%s'."), fileName.c_str());
return false;
}
if (!LoadWAV(data, len, false))
{
delete [] data;
wxLogError(_("Sound file '%s' is in unsupported format."),
fileName.c_str());
return false;
}
return true;
}
bool wxSound::Create(int size, const wxByte* data)
{
wxASSERT( data != NULL );
Free();
if (!LoadWAV(data, size, true))
{
wxLogError(_("Sound data are in unsupported format."));
return false;
}
return true;
}
/*static*/ void wxSound::EnsureBackend()
{
if (!ms_backend)
{
// FIXME -- make this fully dynamic when plugins architecture is in
// place
#if wxUSE_LIBSDL
//if (!ms_backend)
{
#if !wxUSE_PLUGINS
ms_backend = wxCreateSoundBackendSDL();
#else
wxString dllname;
dllname.Printf(_T("%s/%s"),
wxDynamicLibrary::GetPluginsDirectory().c_str(),
wxDynamicLibrary::CanonicalizePluginName(
_T("sound_sdl"), wxDL_PLUGIN_BASE).c_str());
wxLogTrace(_T("sound"),
_T("trying to load SDL plugin from '%s'..."),
dllname.c_str());
wxLogNull null;
ms_backendSDL = new wxDynamicLibrary(dllname, wxDL_NOW);
if (!ms_backendSDL->IsLoaded())
{
wxDELETE(ms_backendSDL);
}
else
{
typedef wxSoundBackend *(*wxCreateSoundBackend_t)();
wxDYNLIB_FUNCTION(wxCreateSoundBackend_t,
wxCreateSoundBackendSDL, *ms_backendSDL);
if (pfnwxCreateSoundBackendSDL)
{
ms_backend = (*pfnwxCreateSoundBackendSDL)();
}
}
#endif
if (ms_backend && !ms_backend->IsAvailable())
{
wxDELETE(ms_backend);
}
}
#endif
#ifdef HAVE_SYS_SOUNDCARD_H
if (!ms_backend)
{
ms_backend = new wxSoundBackendOSS();
if (!ms_backend->IsAvailable())
{
wxDELETE(ms_backend);
}
}
#endif
if (!ms_backend)
ms_backend = new wxSoundBackendNull();
if (!ms_backend->HasNativeAsyncPlayback())
ms_backend = new wxSoundSyncOnlyAdaptor(ms_backend);
wxLogTrace(_T("sound"),
_T("using backend '%s'"), ms_backend->GetName().c_str());
}
}
/*static*/ void wxSound::UnloadBackend()
{
if (ms_backend)
{
wxLogTrace(_T("sound"), _T("unloading backend"));
Stop();
delete ms_backend;
ms_backend = NULL;
#if wxUSE_LIBSDL && wxUSE_PLUGINS
delete ms_backendSDL;
#endif
}
}
bool wxSound::DoPlay(unsigned flags) const
{
wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
EnsureBackend();
wxSoundPlaybackStatus status;
status.m_playing = true;
status.m_stopRequested = false;
return ms_backend->Play(m_data, flags, &status);
}
/*static*/ void wxSound::Stop()
{
if (ms_backend)
ms_backend->Stop();
}
/*static*/ bool wxSound::IsPlaying()
{
if (ms_backend)
return ms_backend->IsPlaying();
else
return false;
}
void wxSound::Free()
{
if (m_data)
m_data->DecRef();
}
typedef struct
{
wxUint32 uiSize;
wxUint16 uiFormatTag;
wxUint16 uiChannels;
wxUint32 ulSamplesPerSec;
wxUint32 ulAvgBytesPerSec;
wxUint16 uiBlockAlign;
wxUint16 uiBitsPerSample;
} WAVEFORMAT;
#define WAVE_FORMAT_PCM 1
#define WAVE_INDEX 8
#define FMT_INDEX 12
bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
{
WAVEFORMAT waveformat;
wxUint32 ul;
if (length < 32 + sizeof(WAVEFORMAT))
return false;
memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
waveformat.uiChannels = wxUINT16_SWAP_ON_BE(waveformat.uiChannels);
waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulSamplesPerSec);
waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulAvgBytesPerSec);
waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
if (memcmp(data, "RIFF", 4) != 0)
return false;
if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
return false;
if (memcmp(&data[FMT_INDEX], "fmt ", 4) != 0)
return false;
if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
return false;
memcpy(&ul,&data[FMT_INDEX + waveformat.uiSize + 12], 4);
ul = wxUINT32_SWAP_ON_BE(ul);
//WAS: if (ul + FMT_INDEX + waveformat.uiSize + 16 != length)
if (ul + FMT_INDEX + waveformat.uiSize + 16 > length)
return false;
if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
return false;
if (waveformat.ulSamplesPerSec !=
waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
return false;
m_data = new wxSoundData;
m_data->m_channels = waveformat.uiChannels;
m_data->m_samplingRate = waveformat.ulSamplesPerSec;
m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
m_data->m_samples = ul / (m_data->m_channels * m_data->m_bitsPerSample / 8);
m_data->m_dataBytes = ul;
if (copyData)
{
m_data->m_dataWithHeader = new wxUint8[length];
memcpy(m_data->m_dataWithHeader, data, length);
}
else
m_data->m_dataWithHeader = (wxUint8*)data;
m_data->m_data =
(&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
return true;
}
// ----------------------------------------------------------------------------
// wxSoundCleanupModule
// ----------------------------------------------------------------------------
class wxSoundCleanupModule: public wxModule
{
public:
bool OnInit() { return true; }
void OnExit() { wxSound::UnloadBackend(); }
DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule)
};
IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule, wxModule)
#endif