From e16ae0c3e5e9cf9587c2e0c24f695452dcdd4385 Mon Sep 17 00:00:00 2001 From: kervala Date: Tue, 27 Jul 2010 18:52:05 +0200 Subject: [PATCH] Changed: #1030 Implement music streaming in OpenAL driver --- .../include/nel/sound/driver/music_buffer.h | 7 +- .../nel/sound/driver/music_buffer_vorbis.h | 7 +- .../src/sound/driver/music_buffer_vorbis.cpp | 9 +- .../sound/driver/openal/driver_openal.vcproj | 16 +- .../sound/driver/openal/music_channel_al.cpp | 303 ++++++++++++++++++ .../sound/driver/openal/music_channel_al.h | 100 ++++++ code/nel/src/sound/driver/openal/stdopenal.h | 4 + 7 files changed, 436 insertions(+), 10 deletions(-) create mode 100644 code/nel/src/sound/driver/openal/music_channel_al.cpp create mode 100644 code/nel/src/sound/driver/openal/music_channel_al.h diff --git a/code/nel/include/nel/sound/driver/music_buffer.h b/code/nel/include/nel/sound/driver/music_buffer.h index a9df6c495..571e27a31 100644 --- a/code/nel/include/nel/sound/driver/music_buffer.h +++ b/code/nel/include/nel/sound/driver/music_buffer.h @@ -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 */ diff --git a/code/nel/include/nel/sound/driver/music_buffer_vorbis.h b/code/nel/include/nel/sound/driver/music_buffer_vorbis.h index 8971a8a74..978f393da 100644 --- a/code/nel/include/nel/sound/driver/music_buffer_vorbis.h +++ b/code/nel/include/nel/sound/driver/music_buffer_vorbis.h @@ -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 */ diff --git a/code/nel/src/sound/driver/music_buffer_vorbis.cpp b/code/nel/src/sound/driver/music_buffer_vorbis.cpp index e9df6d0d1..a02226046 100644 --- a/code/nel/src/sound/driver/music_buffer_vorbis.cpp +++ b/code/nel/src/sound/driver/music_buffer_vorbis.cpp @@ -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 */ diff --git a/code/nel/src/sound/driver/openal/driver_openal.vcproj b/code/nel/src/sound/driver/openal/driver_openal.vcproj index f0607d2d3..13fc8c716 100644 --- a/code/nel/src/sound/driver/openal/driver_openal.vcproj +++ b/code/nel/src/sound/driver/openal/driver_openal.vcproj @@ -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" > + + + + diff --git a/code/nel/src/sound/driver/openal/music_channel_al.cpp b/code/nel/src/sound/driver/openal/music_channel_al.cpp new file mode 100644 index 000000000..c7146ea3a --- /dev/null +++ b/code/nel/src/sound/driver/openal/music_channel_al.cpp @@ -0,0 +1,303 @@ +// NeL - MMORPG Framework +// 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 . + +#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(_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 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 */ diff --git a/code/nel/src/sound/driver/openal/music_channel_al.h b/code/nel/src/sound/driver/openal/music_channel_al.h new file mode 100644 index 000000000..dddb5a052 --- /dev/null +++ b/code/nel/src/sound/driver/openal/music_channel_al.h @@ -0,0 +1,100 @@ +// NeL - MMORPG Framework +// 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 . + +#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 */ diff --git a/code/nel/src/sound/driver/openal/stdopenal.h b/code/nel/src/sound/driver/openal/stdopenal.h index bbdb9e923..12960d57f 100644 --- a/code/nel/src/sound/driver/openal/stdopenal.h +++ b/code/nel/src/sound/driver/openal/stdopenal.h @@ -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 */