Changed: #1030 Implement music streaming in OpenAL driver

This commit is contained in:
kervala 2010-07-27 18:52:05 +02:00
parent f4a2d07919
commit e16ae0c3e5
7 changed files with 436 additions and 10 deletions

View file

@ -94,19 +94,22 @@ public:
virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum) =0;
/// Get the amount of channels (2 is stereo) in output.
virtual uint16 getChannels() =0;
virtual uint8 getChannels() =0;
/// Get the samples per second (often 44100) in output.
virtual uint32 getSamplesPerSec() =0;
/// Get the bits per sample (often 16) in output.
virtual uint16 getBitsPerSample() =0;
virtual uint8 getBitsPerSample() =0;
/// Get if the music has ended playing (never true if loop).
virtual bool isMusicEnded() =0;
/// Get the total time in seconds.
virtual float getLength() =0;
/// Get the size of uncompressed data in bytes.
virtual uint getUncompressedSize() =0;
}; /* class IMusicBuffer */
} /* namespace NLSOUND */

View file

@ -75,19 +75,22 @@ public:
virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum);
/// Get the amount of channels (2 is stereo) in output.
virtual uint16 getChannels();
virtual uint8 getChannels();
/// Get the samples per second (often 44100) in output.
virtual uint32 getSamplesPerSec();
/// Get the bits per sample (often 16) in output.
virtual uint16 getBitsPerSample();
virtual uint8 getBitsPerSample();
/// Get if the music has ended playing (never true if loop).
virtual bool isMusicEnded();
/// Get the total time in seconds.
virtual float getLength();
/// Get the size of uncompressed data in bytes.
virtual uint getUncompressedSize();
}; /* class CMusicBufferVorbis */
} /* namespace NLSOUND */

View file

@ -158,7 +158,7 @@ uint32 CMusicBufferVorbis::getNextBytes(uint8 *buffer, uint32 minimum, uint32 ma
return bytes_read;
}
uint16 CMusicBufferVorbis::getChannels()
uint8 CMusicBufferVorbis::getChannels()
{
vorbis_info *vi = ov_info(&_OggVorbisFile, -1);
return (uint16)vi->channels;
@ -170,7 +170,7 @@ uint32 CMusicBufferVorbis::getSamplesPerSec()
return vi->rate;
}
uint16 CMusicBufferVorbis::getBitsPerSample()
uint8 CMusicBufferVorbis::getBitsPerSample()
{
return 16;
}
@ -185,6 +185,11 @@ float CMusicBufferVorbis::getLength()
return (float)ov_time_total(&_OggVorbisFile, -1);
}
uint CMusicBufferVorbis::getUncompressedSize()
{
return (uint)ov_pcm_total(&_OggVorbisFile, -1) * (getBitsPerSample() / 2) * getChannels();
}
} /* namespace NLSOUND */
/* end of file */

View file

@ -74,7 +74,7 @@
Name="VCLinkerTool"
AdditionalDependencies="OpenAL32.lib EFX-Util.lib"
OutputFile="..\..\..\..\lib\nel_drv_openal_win_d.dll"
IgnoreDefaultLibraryNames="msvcrt.lib;libcmt.lib"
IgnoreDefaultLibraryNames="msvcrt.lib"
ModuleDefinitionFile="$(ProjectName).def"
GenerateDebugInformation="true"
SubSystem="2"
@ -159,7 +159,7 @@
Name="VCLinkerTool"
AdditionalDependencies="OpenAL32.lib EFX-Util.lib"
OutputFile="..\..\..\..\lib64\nel_drv_openal_win_d.dll"
IgnoreDefaultLibraryNames="msvcrt.lib;libcmt.lib"
IgnoreDefaultLibraryNames="msvcrt.lib"
ModuleDefinitionFile="$(ProjectName).def"
GenerateDebugInformation="true"
SubSystem="2"
@ -247,7 +247,7 @@
Name="VCLinkerTool"
AdditionalDependencies="OpenAL32.lib EFX-Util.lib"
OutputFile="..\..\..\..\lib\nel_drv_openal_win_r.dll"
IgnoreDefaultLibraryNames="libcmt.lib"
IgnoreDefaultLibraryNames=""
ModuleDefinitionFile="$(ProjectName).def"
GenerateDebugInformation="true"
SubSystem="2"
@ -336,7 +336,7 @@
Name="VCLinkerTool"
AdditionalDependencies="OpenAL32.lib EFX-Util.lib"
OutputFile="..\..\..\..\lib64\nel_drv_openal_win_r.dll"
IgnoreDefaultLibraryNames="libcmt.lib"
IgnoreDefaultLibraryNames=""
ModuleDefinitionFile="$(ProjectName).def"
GenerateDebugInformation="true"
SubSystem="2"
@ -401,6 +401,14 @@
RelativePath=".\listener_al.h"
>
</File>
<File
RelativePath=".\music_channel_al.cpp"
>
</File>
<File
RelativePath=".\music_channel_al.h"
>
</File>
<File
RelativePath=".\source_al.cpp"
>

View file

@ -0,0 +1,303 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdopenal.h"
// Project includes
#include "sound_driver_al.h"
#include "source_al.h"
#include "buffer_al.h"
#include "music_channel_al.h"
using namespace std;
using namespace NLMISC;
namespace NLSOUND
{
CMusicChannelAL::CMusicChannelAL(CSoundDriverAL *soundDriver)
: _MusicBuffer(NULL), _SoundDriver(soundDriver), _Gain(1.0), _Source(NULL), _Thread(NULL), _Async(false), _Playing(false), _Buffer(NULL)
{
// create a default source for music streaming
_Source = static_cast<CSourceAL*>(_SoundDriver->createSource());
_Source->setPos(CVector(0, 0, 0));
_Source->setVelocity(CVector(0, 0, 0));
_Source->setDirection(CVector(0, 0, 0));
_Source->setSourceRelativeMode(true);
_Source->setStreamingBuffersMax(4);
_Source->setStreamingBufferSize(32768);
// _Source->setStreaming(true);
}
CMusicChannelAL::~CMusicChannelAL()
{
release();
if (_SoundDriver) { _SoundDriver->removeMusicChannel(this); _SoundDriver = NULL; }
}
void CMusicChannelAL::release()
{
// stop thread before deleting it
stop();
// delete thread
if (_Thread)
{
delete _Thread;
_Thread = NULL;
}
// delete source
if (_Source)
{
delete _Source;
_Source = NULL;
}
}
/// Fill IBuffer with data from IMusicBuffer
bool CMusicChannelAL::fillBuffer(IBuffer *buffer, uint length)
{
if (!buffer || !length)
{
nlwarning("AL: No data to stream");
return false;
}
// fill buffer with music data
uint8 *tmp = buffer->lock(length);
if (tmp == NULL)
{
nlwarning("AL: Can't allocate %u bytes for buffer", length);
return false;
}
uint32 size = _MusicBuffer->getNextBytes(tmp, length, length);
buffer->unlock(size);
// add buffer to streaming buffers queue
_Source->submitStreamingBuffer(buffer);
return true;
}
/// Use buffer format from IMusicBuffer
void CMusicChannelAL::setBufferFormat(IBuffer *buffer)
{
if (!buffer)
{
nlwarning("AL: No buffer specified");
return;
}
// use the same format as music for buffers
buffer->setFormat(IBuffer::FormatPcm, _MusicBuffer->getChannels(),
_MusicBuffer->getBitsPerSample(), _MusicBuffer->getSamplesPerSec());
}
void CMusicChannelAL::run()
{
if (_Async)
{
bool first = true;
// use queued buffers
do
{
// buffers to update
std::vector<CBufferAL*> buffers;
if (first)
{
// get all buffers to queue
_Source->getStreamingBuffers(buffers);
// set format for each buffer
for(uint i = 0; i < buffers.size(); ++i)
setBufferFormat(buffers[i]);
}
else
{
// get unqueued buffers
_Source->getProcessedStreamingBuffers(buffers);
}
// fill buffers
for(uint i = 0; i < buffers.size(); ++i)
fillBuffer(buffers[i], _Source->getStreamingBufferSize());
// play the source
if (first)
{
_Source->play();
first = false;
}
// wait 100ms before rechecking buffers
nlSleep(100);
}
while(!_MusicBuffer->isMusicEnded() && _Playing);
}
else
{
// use an unique buffer managed by CMusicChannelAL
_Buffer = _SoundDriver->createBuffer();
// set format
setBufferFormat(_Buffer);
// fill data
fillBuffer(_Buffer, _MusicBuffer->getUncompressedSize());
// we don't need _MusicBuffer anymore because all is loaded into memory
if (_MusicBuffer)
{
delete _MusicBuffer;
_MusicBuffer = NULL;
}
// use this buffer as source
_Source->setStaticBuffer(_Buffer);
// play the source
_Source->play();
}
// music finished without interruption
if (_Playing)
{
// wait until source is not playing
while(_Source->isPlaying() && _Playing) nlSleep(1000);
_Source->stop();
_Playing = false;
}
}
/** Play some music (.ogg etc...)
* NB: if an old music was played, it is first stop with stopMusic()
* \param filepath file path, CPath::lookup is done here
* \param async stream music from hard disk, preload in memory if false
* \param loop must be true to play the music in loop.
*/
bool CMusicChannelAL::play(const std::string &filepath, bool async, bool loop)
{
// stop a previous music
stop();
// when not using async, we must load the whole file once
_MusicBuffer = IMusicBuffer::createMusicBuffer(filepath, async, async ? loop:false);
if (_MusicBuffer)
{
// create the thread if it's not yet created
if (!_Thread) _Thread = IThread::create(this);
if (!_Thread)
{
nlwarning("AL: Can't create a new thread");
return false;
}
_Async = async;
_Playing = true;
// we need to loop the source only if not async
_Source->setLooping(async ? false:loop);
// start the thread
_Thread->start();
}
else
{
nlwarning("AL: Can't stream file %s", filepath.c_str());
return false;
}
return true;
}
/// Stop the music previously loaded and played (the Memory is also freed)
void CMusicChannelAL::stop()
{
_Playing = false;
_Source->stop();
// if not using async streaming, we manage static buffer ourself
if (!_Async && _Buffer)
{
_Source->setStaticBuffer(NULL);
delete _Buffer;
_Buffer = NULL;
}
// wait until thread is finished
if (_Thread)
_Thread->wait();
if (_MusicBuffer)
{
delete _MusicBuffer;
_MusicBuffer = NULL;
}
}
/// Pause the music previously loaded and played (the Memory is not freed)
void CMusicChannelAL::pause()
{
_Source->pause();
}
/// Resume the music previously paused
void CMusicChannelAL::resume()
{
_Source->play();
}
/// Return true if a song is finished.
bool CMusicChannelAL::isEnded()
{
return !_Playing;
}
/// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading
bool CMusicChannelAL::isLoadingAsync()
{
return _Async && _Playing;
}
/// Return the total length (in second) of the music currently played
float CMusicChannelAL::getLength()
{
if (_MusicBuffer) return _MusicBuffer->getLength();
else return .0f;
}
/** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1)
* NB: in OpenAL driver, the volume of music IS affected by IListener::setGain()
*/
void CMusicChannelAL::setVolume(float gain)
{
_Gain = gain;
_Source->setGain(gain);
}
} /* namespace NLSOUND */
/* end of file */

View file

@ -0,0 +1,100 @@
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef NLSOUND_MUSIC_CHANNEL_AL_H
#define NLSOUND_MUSIC_CHANNEL_AL_H
#include "nel/sound/driver/music_channel.h"
namespace NLSOUND
{
class CSoundDriverAL;
class IMusicBuffer;
/**
* \brief CMusicChannelAL
* \date 2010-07-27 16:56GMT
* \author Kervala
* CMusicChannelAL is an implementation of the IMusicChannel interface to run on OpenAL.
*/
class CMusicChannelAL : public IMusicChannel, public NLMISC::IRunnable
{
protected:
// outside pointers
CSoundDriverAL* _SoundDriver;
// pointers
IMusicBuffer* _MusicBuffer;
NLMISC::IThread* _Thread;
IBuffer* _Buffer;
CSourceAL* _Source;
bool _Playing;
bool _Async;
float _Gain;
/// Fill IBuffer with data from IMusicBuffer
bool fillBuffer(IBuffer *buffer, uint length);
/// Use buffer format from IMusicBuffer
void setBufferFormat(IBuffer *buffer);
/// Declared in NLMISC::IRunnable interface
virtual void run();
public:
CMusicChannelAL(CSoundDriverAL *soundDriver);
virtual ~CMusicChannelAL();
void release();
/** Play some music (.ogg etc...)
* NB: if an old music was played, it is first stop with stopMusic()
* \param filepath file path, CPath::lookup is done here
* \param async stream music from hard disk, preload in memory if false
* \param loop must be true to play the music in loop.
*/
virtual bool play(const std::string &filepath, bool async, bool loop);
/// Stop the music previously loaded and played (the Memory is also freed)
virtual void stop();
/// Pause the music previously loaded and played (the Memory is not freed)
virtual void pause();
/// Resume the music previously paused
virtual void resume();
/// Return true if a song is finished.
virtual bool isEnded();
/// Return true if the song is still loading asynchronously and hasn't started playing yet (false if not async), used to delay fading
virtual bool isLoadingAsync();
/// Return the total length (in second) of the music currently played
virtual float getLength();
/** Set the music volume (if any music played). (volume value inside [0 , 1]) (default: 1)
* NB: in OpenAL driver, the volume of music IS affected by IListener::setGain()
*/
virtual void setVolume(float gain);
}; /* class CMusicChannelAL */
} /* namespace NLSOUND */
#endif /* #ifndef NLSOUND_MUSIC_CHANNEL_AL_H */
/* end of file */

View file

@ -50,10 +50,14 @@
#include "nel/misc/fast_mem.h"
#include "nel/misc/path.h"
#include "nel/misc/dynloadlib.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/thread.h"
#include "nel/sound/driver/sound_driver.h"
#include "nel/sound/driver/buffer.h"
#include "nel/sound/driver/source.h"
#include "nel/sound/driver/listener.h"
#include "nel/sound/driver/effect.h"
#include "nel/sound/driver/music_buffer.h"
/* end of file */