Abstract Ogg Vorbis sound decoding into the SoundStream class. Note: the SoundStream class is deliberately not exposed to scripts, as it requires low-level data access and is used from the sound mixing thread.
This commit is contained in:
Родитель
4bf765d711
Коммит
acb993e4ba
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// Copyright (c) 2008-2014 the Urho3D project.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
#include "Precompiled.h"
|
||||
#include "OggVorbisSoundStream.h"
|
||||
#include "Sound.h"
|
||||
|
||||
#include <stb_vorbis.h>
|
||||
|
||||
#include "DebugNew.h"
|
||||
|
||||
namespace Urho3D
|
||||
{
|
||||
|
||||
OggVorbisSoundStream::OggVorbisSoundStream(const Sound* sound)
|
||||
{
|
||||
assert(sound && sound->IsCompressed());
|
||||
|
||||
SetFormat(sound->GetIntFrequency(), sound->IsSixteenBit(), sound->IsStereo());
|
||||
// If the sound is looped, the stream will automatically rewind at end
|
||||
SetStopAtEnd(!sound->IsLooped());
|
||||
|
||||
// Initialize decoder
|
||||
data_ = sound->GetData();
|
||||
dataSize_ = sound->GetDataSize();
|
||||
int error;
|
||||
decoder_ = stb_vorbis_open_memory((unsigned char*)data_.Get(), dataSize_, &error, 0);
|
||||
}
|
||||
|
||||
OggVorbisSoundStream::~OggVorbisSoundStream()
|
||||
{
|
||||
// Close decoder
|
||||
if (decoder_)
|
||||
{
|
||||
stb_vorbis* vorbis = static_cast<stb_vorbis*>(decoder_);
|
||||
|
||||
stb_vorbis_close(vorbis);
|
||||
decoder_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned OggVorbisSoundStream::GetData(signed char* dest, unsigned numBytes)
|
||||
{
|
||||
if (!decoder_)
|
||||
return 0;
|
||||
|
||||
stb_vorbis* vorbis = static_cast<stb_vorbis*>(decoder_);
|
||||
|
||||
unsigned channels = stereo_ ? 2 : 1;
|
||||
unsigned outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, channels, (short*)dest, numBytes >> 1);
|
||||
|
||||
// Rewind and retry if produced no output and should loop
|
||||
if (!outSamples && !stopAtEnd_)
|
||||
{
|
||||
stb_vorbis_seek_start(vorbis);
|
||||
outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, channels, (short*)dest, numBytes >> 1);
|
||||
}
|
||||
|
||||
return (outSamples * channels) << 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Copyright (c) 2008-2014 the Urho3D project.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ArrayPtr.h"
|
||||
#include "SoundStream.h"
|
||||
|
||||
namespace Urho3D
|
||||
{
|
||||
|
||||
class Sound;
|
||||
|
||||
/// Ogg Vorbis sound stream.
|
||||
class URHO3D_API OggVorbisSoundStream : public SoundStream
|
||||
{
|
||||
public:
|
||||
/// Construct from an Ogg Vorbis compressed sound.
|
||||
OggVorbisSoundStream(const Sound* sound);
|
||||
/// Destruct.
|
||||
~OggVorbisSoundStream();
|
||||
|
||||
/// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
|
||||
virtual unsigned GetData(signed char* dest, unsigned numBytes);
|
||||
|
||||
protected:
|
||||
/// Decoder state.
|
||||
void* decoder_;
|
||||
/// Compressed sound data.
|
||||
SharedArrayPtr<signed char> data_;
|
||||
/// Compressed sound data size in bytes.
|
||||
unsigned dataSize_;
|
||||
};
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
#include "Context.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Log.h"
|
||||
#include "OggVorbisSoundStream.h"
|
||||
#include "Profiler.h"
|
||||
#include "ResourceCache.h"
|
||||
#include "Sound.h"
|
||||
|
@ -309,43 +310,9 @@ void Sound::FixInterpolation()
|
|||
}
|
||||
}
|
||||
|
||||
void* Sound::AllocateDecoder()
|
||||
SharedPtr<SoundStream> Sound::GetDecoderStream() const
|
||||
{
|
||||
if (!compressed_)
|
||||
return 0;
|
||||
|
||||
int error;
|
||||
stb_vorbis* vorbis = stb_vorbis_open_memory((unsigned char*)data_.Get(), dataSize_, &error, 0);
|
||||
return vorbis;
|
||||
}
|
||||
|
||||
unsigned Sound::Decode(void* decoder, signed char* dest, unsigned bytes)
|
||||
{
|
||||
if (!decoder)
|
||||
return 0;
|
||||
|
||||
unsigned soundSources = stereo_ ? 2 : 1;
|
||||
stb_vorbis* vorbis = static_cast<stb_vorbis*>(decoder);
|
||||
unsigned outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, soundSources, (short*)dest, bytes >> 1);
|
||||
return (outSamples * soundSources) << 1;
|
||||
}
|
||||
|
||||
void Sound::RewindDecoder(void* decoder)
|
||||
{
|
||||
if (!decoder)
|
||||
return;
|
||||
|
||||
stb_vorbis* vorbis = static_cast<stb_vorbis*>(decoder);
|
||||
stb_vorbis_seek_start(vorbis);
|
||||
}
|
||||
|
||||
void Sound::FreeDecoder(void* decoder)
|
||||
{
|
||||
if (!decoder)
|
||||
return;
|
||||
|
||||
stb_vorbis* vorbis = static_cast<stb_vorbis*>(decoder);
|
||||
stb_vorbis_close(vorbis);
|
||||
return compressed_ ? SharedPtr<SoundStream>(new OggVorbisSoundStream(this)) : SharedPtr<SoundStream>();
|
||||
}
|
||||
|
||||
float Sound::GetLength() const
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
namespace Urho3D
|
||||
{
|
||||
|
||||
class SoundStream;
|
||||
|
||||
/// %Sound resource.
|
||||
class URHO3D_API Sound : public Resource
|
||||
{
|
||||
|
@ -63,15 +65,10 @@ public:
|
|||
/// Fix interpolation by copying data from loop start to loop end (looped), or adding silence (oneshot.)
|
||||
void FixInterpolation();
|
||||
|
||||
/// Create and return a compressed audio decoder instance. Return null if fails.
|
||||
void* AllocateDecoder();
|
||||
/// Decode compressed audio data. Return number of actually decoded bytes.
|
||||
unsigned Decode(void* decoder, signed char* dest, unsigned bytes);
|
||||
/// Rewind the decoder to beginning of audio data.
|
||||
void RewindDecoder(void* decoder);
|
||||
/// Free the decoder instance.
|
||||
void FreeDecoder(void* decoder);
|
||||
|
||||
/// Return a new instance of a decoder sound stream. Used by compressed sounds.
|
||||
SharedPtr<SoundStream> GetDecoderStream() const;
|
||||
/// Return shared sound data.
|
||||
SharedArrayPtr<signed char> GetData() const { return data_; }
|
||||
/// Return sound data start.
|
||||
signed char* GetStart() const { return data_.Get(); }
|
||||
/// Return loop start.
|
||||
|
@ -85,16 +82,16 @@ public:
|
|||
/// Return sample size.
|
||||
unsigned GetSampleSize() const;
|
||||
/// Return default frequency as a float.
|
||||
float GetFrequency() { return (float)frequency_; }
|
||||
float GetFrequency() const { return (float)frequency_; }
|
||||
/// Return default frequency as an integer.
|
||||
unsigned GetIntFrequency() { return frequency_; }
|
||||
unsigned GetIntFrequency() const { return frequency_; }
|
||||
/// Return whether is looped.
|
||||
bool IsLooped() const { return looped_; }
|
||||
/// Return whether data is sixteen bit.
|
||||
bool IsSixteenBit() const { return sixteenBit_; }
|
||||
/// Return whether data is stereo.
|
||||
bool IsStereo() const { return stereo_; }
|
||||
/// Return whether is compressed in Ogg Vorbis format.
|
||||
/// Return whether is compressed.
|
||||
bool IsCompressed() const { return compressed_; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "ResourceCache.h"
|
||||
#include "Sound.h"
|
||||
#include "SoundSource.h"
|
||||
#include "SoundStream.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
|
@ -115,8 +116,8 @@ SoundSource::SoundSource(Context* context) :
|
|||
position_(0),
|
||||
fractPosition_(0),
|
||||
timePosition_(0.0f),
|
||||
decoder_(0),
|
||||
decodePosition_(0)
|
||||
streamWritePosition_(0),
|
||||
streamStopped_(false)
|
||||
{
|
||||
audio_ = GetSubsystem<Audio>();
|
||||
|
||||
|
@ -128,8 +129,6 @@ SoundSource::~SoundSource()
|
|||
{
|
||||
if (audio_)
|
||||
audio_->RemoveSoundSource(this);
|
||||
|
||||
FreeDecoder();
|
||||
}
|
||||
|
||||
void SoundSource::RegisterObject(Context* context)
|
||||
|
@ -152,7 +151,7 @@ void SoundSource::Play(Sound* sound)
|
|||
{
|
||||
if (!audio_)
|
||||
return;
|
||||
|
||||
|
||||
// If no frequency set yet, set from the sound's default
|
||||
if (frequency_ == 0.0f && sound)
|
||||
SetFrequency(sound->GetFrequency());
|
||||
|
@ -190,6 +189,34 @@ void SoundSource::Play(Sound* sound, float frequency, float gain, float panning)
|
|||
Play(sound);
|
||||
}
|
||||
|
||||
void SoundSource::Play(SoundStream* stream)
|
||||
{
|
||||
if (!audio_)
|
||||
return;
|
||||
|
||||
// If no frequency set yet, set from the stream's default
|
||||
if (frequency_ == 0.0f && stream)
|
||||
SetFrequency(stream->GetFrequency());
|
||||
|
||||
SharedPtr<SoundStream> streamPtr(stream);
|
||||
|
||||
// If sound source is currently playing, have to lock the audio mutex. When stream playback is explicitly
|
||||
// requested, clear the existing sound if any
|
||||
if (position_)
|
||||
{
|
||||
MutexLock lock(audio_->GetMutex());
|
||||
sound_.Reset();
|
||||
PlayLockless(streamPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
sound_.Reset();
|
||||
PlayLockless(streamPtr);
|
||||
}
|
||||
|
||||
// Stream playback is not supported for network replication, no need to mark network dirty
|
||||
}
|
||||
|
||||
void SoundSource::Stop()
|
||||
{
|
||||
if (!audio_)
|
||||
|
@ -201,10 +228,9 @@ void SoundSource::Stop()
|
|||
MutexLock lock(audio_->GetMutex());
|
||||
StopLockless();
|
||||
}
|
||||
|
||||
// Free the compressed sound decoder now if any
|
||||
FreeDecoder();
|
||||
|
||||
else
|
||||
StopLockless();
|
||||
|
||||
MarkNetworkUpdate();
|
||||
}
|
||||
|
||||
|
@ -248,90 +274,19 @@ void SoundSource::SetAutoRemove(bool enable)
|
|||
|
||||
bool SoundSource::IsPlaying() const
|
||||
{
|
||||
return sound_ != 0 && position_ != 0;
|
||||
return (sound_ || soundStream_) && position_ != 0;
|
||||
}
|
||||
|
||||
void SoundSource::SetPlayPosition(signed char* pos)
|
||||
{
|
||||
if (!audio_ || !sound_)
|
||||
// Setting play position on a stream is not supported
|
||||
if (!audio_ || !sound_ || soundStream_)
|
||||
return;
|
||||
|
||||
MutexLock lock(audio_->GetMutex());
|
||||
SetPlayPositionLockless(pos);
|
||||
}
|
||||
|
||||
void SoundSource::PlayLockless(Sound* sound)
|
||||
{
|
||||
// Reset the time position in any case
|
||||
timePosition_ = 0.0f;
|
||||
|
||||
if (sound)
|
||||
{
|
||||
if (!sound->IsCompressed())
|
||||
{
|
||||
// Uncompressed sound start
|
||||
signed char* start = sound->GetStart();
|
||||
if (start)
|
||||
{
|
||||
// Free decoder in case previous sound was compressed
|
||||
FreeDecoder();
|
||||
sound_ = sound;
|
||||
position_ = start;
|
||||
fractPosition_ = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compressed sound start
|
||||
if (sound == sound_ && decoder_)
|
||||
{
|
||||
// If same compressed sound is already playing, rewind the decoder
|
||||
sound_->RewindDecoder(decoder_);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else just set the new sound with a dummy start position. The mixing routine will allocate the new decoder
|
||||
FreeDecoder();
|
||||
sound_ = sound;
|
||||
position_ = sound->GetStart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If sound pointer is null or if sound has no data, stop playback
|
||||
FreeDecoder();
|
||||
sound_.Reset();
|
||||
position_ = 0;
|
||||
}
|
||||
|
||||
void SoundSource::StopLockless()
|
||||
{
|
||||
position_ = 0;
|
||||
timePosition_ = 0.0f;
|
||||
}
|
||||
|
||||
void SoundSource::SetPlayPositionLockless(signed char* pos)
|
||||
{
|
||||
// Setting position on a compressed sound is not supported
|
||||
if (!sound_ || sound_->IsCompressed())
|
||||
return;
|
||||
|
||||
signed char* start = sound_->GetStart();
|
||||
signed char* end = sound_->GetEnd();
|
||||
if (pos < start)
|
||||
pos = start;
|
||||
if (sound_->IsSixteenBit() && (pos - start) & 1)
|
||||
++pos;
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
position_ = pos;
|
||||
timePosition_ = ((float)(int)(size_t)(pos - sound_->GetStart())) / (sound_->GetSampleSize() * sound_->GetFrequency());
|
||||
}
|
||||
|
||||
void SoundSource::Update(float timeStep)
|
||||
{
|
||||
if (!audio_ || !IsEnabledEffective())
|
||||
|
@ -341,9 +296,9 @@ void SoundSource::Update(float timeStep)
|
|||
if (!audio_->IsInitialized())
|
||||
MixNull(timeStep);
|
||||
|
||||
// Free the decoder if playback has stopped
|
||||
if (!position_ && decoder_)
|
||||
FreeDecoder();
|
||||
// Free the stream if playback has stopped
|
||||
if (soundStream_ && !position_)
|
||||
StopLockless();
|
||||
|
||||
// Check for autoremove
|
||||
if (autoRemove_)
|
||||
|
@ -365,92 +320,73 @@ void SoundSource::Update(float timeStep)
|
|||
|
||||
void SoundSource::Mix(int* dest, unsigned samples, int mixRate, bool stereo, bool interpolation)
|
||||
{
|
||||
if (!position_ || !sound_ || !IsEnabledEffective())
|
||||
if (!position_ || (!sound_ && !soundStream_) || !IsEnabledEffective())
|
||||
return;
|
||||
|
||||
if (sound_->IsCompressed())
|
||||
|
||||
if (soundStream_ && streamBuffer_)
|
||||
{
|
||||
if (decoder_)
|
||||
unsigned streamBufferSize = streamBuffer_->GetDataSize();
|
||||
|
||||
// Decode new audio in stream mode. If stream experienced an underrun, try restarting
|
||||
if (streamStopped_)
|
||||
{
|
||||
// If decoder already exists, decode new compressed audio
|
||||
bool eof = false;
|
||||
unsigned currentPos = position_ - decodeBuffer_->GetStart();
|
||||
unsigned totalBytes;
|
||||
|
||||
// Handle possible wraparound
|
||||
if (currentPos >= decodePosition_)
|
||||
totalBytes = currentPos - decodePosition_;
|
||||
// If stream should stop at end, do not loop after current buffer is played
|
||||
if (soundStream_->GetStopAtEnd())
|
||||
streamBuffer_->SetLooped(false);
|
||||
else
|
||||
totalBytes = decodeBuffer_->GetDataSize() - decodePosition_ + currentPos;
|
||||
|
||||
while (totalBytes)
|
||||
{
|
||||
// Calculate size of current decode work unit (may need to do in two parts if wrapping)
|
||||
unsigned bytes = decodeBuffer_->GetDataSize() - decodePosition_;
|
||||
if (bytes > totalBytes)
|
||||
bytes = totalBytes;
|
||||
|
||||
unsigned outBytes = 0;
|
||||
|
||||
if (!eof)
|
||||
unsigned outBytes = soundStream_->GetData(streamBuffer_->GetStart(), streamBufferSize);
|
||||
if (outBytes)
|
||||
{
|
||||
outBytes = sound_->Decode(decoder_, decodeBuffer_->GetStart() + decodePosition_, bytes);
|
||||
// If decoded less than the requested amount, has reached end. Rewind (looped) or fill rest with zero (oneshot)
|
||||
if (outBytes < bytes)
|
||||
{
|
||||
if (sound_->IsLooped())
|
||||
{
|
||||
sound_->RewindDecoder(decoder_);
|
||||
timePosition_ = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
decodeBuffer_->SetLooped(false); // Stop after the current decode buffer has been played
|
||||
eof = true;
|
||||
}
|
||||
}
|
||||
// If did not get a full buffer, fill the rest with zero
|
||||
if (outBytes < streamBufferSize)
|
||||
memset(streamBuffer_->GetStart() + outBytes, 0, streamBufferSize - outBytes);
|
||||
streamStopped_ = false;
|
||||
// Start playback from beginning of stream buffer again so that there is minimal latency
|
||||
position_ = streamBuffer_->GetStart();
|
||||
fractPosition_ = 0;
|
||||
streamWritePosition_ = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(decodeBuffer_->GetStart() + decodePosition_, 0, bytes);
|
||||
outBytes = bytes;
|
||||
}
|
||||
|
||||
decodePosition_ += outBytes;
|
||||
decodePosition_ %= decodeBuffer_->GetDataSize();
|
||||
totalBytes -= outBytes;
|
||||
}
|
||||
|
||||
// Correct interpolation of the looping buffer
|
||||
decodeBuffer_->FixInterpolation();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Setup the decoder and decode initial buffer
|
||||
decoder_ = sound_->AllocateDecoder();
|
||||
unsigned sampleSize = sound_->GetSampleSize();
|
||||
unsigned decodeBufferSize = sampleSize * sound_->GetIntFrequency() * DECODE_BUFFER_LENGTH / 1000;
|
||||
decodeBuffer_ = new Sound(context_);
|
||||
decodeBuffer_->SetSize(decodeBufferSize);
|
||||
decodeBuffer_->SetFormat(sound_->GetIntFrequency(), true, sound_->IsStereo());
|
||||
|
||||
// Clear the decode buffer, then fill with initial audio data and set it to loop
|
||||
memset(decodeBuffer_->GetStart(), 0, decodeBufferSize);
|
||||
sound_->Decode(decoder_, decodeBuffer_->GetStart(), decodeBufferSize);
|
||||
decodeBuffer_->SetLooped(true);
|
||||
decodePosition_ = 0;
|
||||
|
||||
// Start playing the decode buffer
|
||||
position_ = decodeBuffer_->GetStart();
|
||||
fractPosition_ = 0;
|
||||
unsigned currentPos = position_ - streamBuffer_->GetStart();
|
||||
unsigned totalBytes;
|
||||
|
||||
// Correct initial interpolation of the looping buffer
|
||||
decodeBuffer_->FixInterpolation();
|
||||
// Handle possible wraparound
|
||||
if (currentPos >= streamWritePosition_)
|
||||
totalBytes = currentPos - streamWritePosition_;
|
||||
else
|
||||
totalBytes = streamBuffer_->GetDataSize() - streamWritePosition_ + currentPos;
|
||||
|
||||
while (totalBytes)
|
||||
{
|
||||
// Calculate size of current stream data request (may need to do in two parts if wrapping)
|
||||
unsigned bytes = streamBuffer_->GetDataSize() - streamWritePosition_;
|
||||
if (bytes > totalBytes)
|
||||
bytes = totalBytes;
|
||||
|
||||
unsigned outBytes = soundStream_->GetData(streamBuffer_->GetStart() + streamWritePosition_, bytes);
|
||||
// If got less than the requested amount, stream reached end or experienced underrun. Fill rest with zero
|
||||
if (outBytes < bytes)
|
||||
{
|
||||
streamStopped_ = true;
|
||||
memset(streamBuffer_->GetStart() + streamWritePosition_ + outBytes, 0, bytes - outBytes);
|
||||
}
|
||||
|
||||
streamWritePosition_ += bytes;
|
||||
streamWritePosition_ %= streamBuffer_->GetDataSize();
|
||||
totalBytes -= bytes;
|
||||
}
|
||||
}
|
||||
|
||||
// Correct interpolation of the stream buffer
|
||||
streamBuffer_->FixInterpolation();
|
||||
}
|
||||
|
||||
// If compressed, play the decode buffer. Otherwise play the original sound
|
||||
Sound* sound = sound_->IsCompressed() ? decodeBuffer_ : sound_;
|
||||
// If streaming, play the stream buffer. Otherwise play the original sound
|
||||
Sound* sound = soundStream_ ? streamBuffer_ : sound_;
|
||||
if (!sound)
|
||||
return;
|
||||
|
||||
|
@ -491,10 +427,10 @@ void SoundSource::Mix(int* dest, unsigned samples, int mixRate, bool stereo, boo
|
|||
}
|
||||
|
||||
// Update the time position
|
||||
if (!sound_->IsCompressed())
|
||||
if (soundStream_)
|
||||
timePosition_ += ((float)samples / (float)mixRate) * frequency_ / soundStream_->GetFrequency();
|
||||
else if (sound_)
|
||||
timePosition_ = ((float)(int)(size_t)(position_ - sound_->GetStart())) / (sound_->GetSampleSize() * sound_->GetFrequency());
|
||||
else
|
||||
timePosition_ += ((float)samples / (float)mixRate) * frequency_ / sound_->GetFrequency();
|
||||
}
|
||||
|
||||
void SoundSource::SetSoundAttr(ResourceRef value)
|
||||
|
@ -505,8 +441,9 @@ void SoundSource::SetSoundAttr(ResourceRef value)
|
|||
Play(newSound);
|
||||
else
|
||||
{
|
||||
// When changing the sound and not playing, make sure the old decoder (if any) is freed
|
||||
FreeDecoder();
|
||||
// When changing the sound and not playing, free previous sound stream and stream buffer (if any)
|
||||
soundStream_.Reset();
|
||||
streamBuffer_.Reset();
|
||||
sound_ = newSound;
|
||||
}
|
||||
}
|
||||
|
@ -541,6 +478,104 @@ int SoundSource::GetPositionAttr() const
|
|||
return 0;
|
||||
}
|
||||
|
||||
void SoundSource::PlayLockless(Sound* sound)
|
||||
{
|
||||
// Reset the time position in any case
|
||||
timePosition_ = 0.0f;
|
||||
|
||||
if (sound)
|
||||
{
|
||||
if (!sound->IsCompressed())
|
||||
{
|
||||
// Uncompressed sound start
|
||||
signed char* start = sound->GetStart();
|
||||
if (start)
|
||||
{
|
||||
// Free existing stream & stream buffer if any
|
||||
soundStream_.Reset();
|
||||
streamBuffer_.Reset();
|
||||
sound_ = sound;
|
||||
position_ = start;
|
||||
fractPosition_ = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compressed sound start
|
||||
PlayLockless(sound->GetDecoderStream());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If sound pointer is null or if sound has no data, stop playback
|
||||
StopLockless();
|
||||
sound_.Reset();
|
||||
}
|
||||
|
||||
void SoundSource::PlayLockless(SharedPtr<SoundStream> stream)
|
||||
{
|
||||
// Reset the time position in any case
|
||||
timePosition_ = 0.0f;
|
||||
|
||||
if (stream)
|
||||
{
|
||||
// Setup the stream buffer
|
||||
unsigned sampleSize = stream->GetSampleSize();
|
||||
unsigned streamBufferSize = sampleSize * stream->GetIntFrequency() * STREAM_BUFFER_LENGTH / 1000;
|
||||
|
||||
streamBuffer_ = new Sound(context_);
|
||||
streamBuffer_->SetSize(streamBufferSize);
|
||||
streamBuffer_->SetFormat(stream->GetIntFrequency(), stream->IsSixteenBit(), stream->IsStereo());
|
||||
streamBuffer_->SetLooped(true);
|
||||
|
||||
// Fill stream buffer with initial data
|
||||
unsigned outBytes = stream->GetData(streamBuffer_->GetStart(), streamBufferSize);
|
||||
if (outBytes < streamBufferSize)
|
||||
memset(streamBuffer_->GetStart() + outBytes, 0, streamBufferSize - outBytes);
|
||||
|
||||
soundStream_ = stream;
|
||||
streamWritePosition_ = 0;
|
||||
streamStopped_ = false;
|
||||
position_ = streamBuffer_->GetStart();
|
||||
fractPosition_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// If stream pointer is null, stop playback
|
||||
StopLockless();
|
||||
}
|
||||
|
||||
void SoundSource::StopLockless()
|
||||
{
|
||||
position_ = 0;
|
||||
timePosition_ = 0.0f;
|
||||
streamStopped_ = true;
|
||||
|
||||
// Free the sound stream and decode buffer if a stream was playing
|
||||
soundStream_.Reset();
|
||||
streamBuffer_.Reset();
|
||||
}
|
||||
|
||||
void SoundSource::SetPlayPositionLockless(signed char* pos)
|
||||
{
|
||||
// Setting position on a stream is not supported
|
||||
if (!sound_ || soundStream_)
|
||||
return;
|
||||
|
||||
signed char* start = sound_->GetStart();
|
||||
signed char* end = sound_->GetEnd();
|
||||
if (pos < start)
|
||||
pos = start;
|
||||
if (sound_->IsSixteenBit() && (pos - start) & 1)
|
||||
++pos;
|
||||
if (pos > end)
|
||||
pos = end;
|
||||
|
||||
position_ = pos;
|
||||
timePosition_ = ((float)(int)(size_t)(pos - sound_->GetStart())) / (sound_->GetSampleSize() * sound_->GetFrequency());
|
||||
}
|
||||
|
||||
void SoundSource::MixMonoToMono(Sound* sound, int* dest, unsigned samples, int mixRate)
|
||||
{
|
||||
float totalGain = audio_->GetSoundSourceMasterGain(soundType_) * attenuation_ * gain_;
|
||||
|
@ -1224,15 +1259,4 @@ void SoundSource::MixNull(float timeStep)
|
|||
}
|
||||
}
|
||||
|
||||
void SoundSource::FreeDecoder()
|
||||
{
|
||||
if (sound_ && decoder_)
|
||||
{
|
||||
sound_->FreeDecoder(decoder_);
|
||||
decoder_ = 0;
|
||||
}
|
||||
|
||||
decodeBuffer_.Reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,9 +30,10 @@ namespace Urho3D
|
|||
|
||||
class Audio;
|
||||
class Sound;
|
||||
class SoundStream;
|
||||
|
||||
// Compressed audio decode buffer length in milliseconds
|
||||
static const int DECODE_BUFFER_LENGTH = 100;
|
||||
static const int STREAM_BUFFER_LENGTH = 100;
|
||||
|
||||
/// %Sound source component with stereo position.
|
||||
class URHO3D_API SoundSource : public Component
|
||||
|
@ -55,6 +56,8 @@ public:
|
|||
void Play(Sound* sound, float frequency, float gain);
|
||||
/// Play a sound with specified frequency, gain and panning.
|
||||
void Play(Sound* sound, float frequency, float gain, float panning);
|
||||
/// Start playing a sound stream.
|
||||
void Play(SoundStream* stream);
|
||||
/// Stop playback.
|
||||
void Stop();
|
||||
/// Set sound type, determines the master gain group.
|
||||
|
@ -93,12 +96,6 @@ public:
|
|||
/// Return whether is playing.
|
||||
bool IsPlaying() const;
|
||||
|
||||
/// Play a sound without locking the audio mutex. Called internally.
|
||||
void PlayLockless(Sound* sound);
|
||||
/// Stop sound without locking the audio mutex. Called internally.
|
||||
void StopLockless();
|
||||
/// Set new playback position without locking the audio mutex. Called internally.
|
||||
void SetPlayPositionLockless(signed char* position);
|
||||
/// Update the sound source. Perform subclass specific operations. Called by Audio.
|
||||
virtual void Update(float timeStep);
|
||||
/// Mix sound source output to a 32-bit clipping buffer. Called by Audio.
|
||||
|
@ -134,6 +131,14 @@ protected:
|
|||
bool autoRemove_;
|
||||
|
||||
private:
|
||||
/// Play a sound without locking the audio mutex. Called internally.
|
||||
void PlayLockless(Sound* sound);
|
||||
/// Play a sound stream without locking the audio mutex. Called internally.
|
||||
void PlayLockless(SharedPtr<SoundStream> stream);
|
||||
/// Stop sound without locking the audio mutex. Called internally.
|
||||
void StopLockless();
|
||||
/// Set new playback position without locking the audio mutex. Called internally.
|
||||
void SetPlayPositionLockless(signed char* position);
|
||||
/// Mix mono sample to mono buffer.
|
||||
void MixMonoToMono(Sound* sound, int* dest, unsigned samples, int mixRate);
|
||||
/// Mix mono sample to stereo buffer.
|
||||
|
@ -154,23 +159,23 @@ private:
|
|||
void MixZeroVolume(Sound* sound, unsigned samples, int mixRate);
|
||||
/// Advance playback pointer to simulate audio playback in headless mode.
|
||||
void MixNull(float timeStep);
|
||||
/// Free the decoder if any.
|
||||
void FreeDecoder();
|
||||
|
||||
/// Sound.
|
||||
/// Sound that is being played.
|
||||
SharedPtr<Sound> sound_;
|
||||
/// Sound stream that is being played.
|
||||
SharedPtr<SoundStream> soundStream_;
|
||||
/// Playback position.
|
||||
volatile signed char *position_;
|
||||
/// Playback fractional position.
|
||||
volatile int fractPosition_;
|
||||
/// Playback time position.
|
||||
volatile float timePosition_;
|
||||
/// Ogg Vorbis decoder.
|
||||
void* decoder_;
|
||||
/// Decode buffer.
|
||||
SharedPtr<Sound> decodeBuffer_;
|
||||
/// Previous decode buffer position.
|
||||
unsigned decodePosition_;
|
||||
SharedPtr<Sound> streamBuffer_;
|
||||
/// Position in stream buffer the next audio data from the stream will be written to.
|
||||
unsigned streamWritePosition_;
|
||||
/// Stream underrun flag.
|
||||
bool streamStopped_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// Copyright (c) 2008-2014 the Urho3D project.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
#include "Precompiled.h"
|
||||
#include "SoundStream.h"
|
||||
|
||||
#include "DebugNew.h"
|
||||
|
||||
namespace Urho3D
|
||||
{
|
||||
|
||||
SoundStream::SoundStream() :
|
||||
frequency_(44100),
|
||||
stopAtEnd_(false),
|
||||
sixteenBit_(false),
|
||||
stereo_(false)
|
||||
{
|
||||
}
|
||||
|
||||
SoundStream::~SoundStream()
|
||||
{
|
||||
}
|
||||
|
||||
void SoundStream::SetFormat(unsigned frequency, bool sixteenBit, bool stereo)
|
||||
{
|
||||
frequency_ = frequency;
|
||||
sixteenBit_ = sixteenBit;
|
||||
stereo_ = stereo;
|
||||
}
|
||||
|
||||
void SoundStream::SetStopAtEnd(bool enable)
|
||||
{
|
||||
stopAtEnd_ = enable;
|
||||
}
|
||||
|
||||
unsigned SoundStream::GetSampleSize() const
|
||||
{
|
||||
unsigned size = 1;
|
||||
if (sixteenBit_)
|
||||
size <<= 1;
|
||||
if (stereo_)
|
||||
size <<= 1;
|
||||
return size;
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// Copyright (c) 2008-2014 the Urho3D project.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RefCounted.h"
|
||||
|
||||
namespace Urho3D
|
||||
{
|
||||
|
||||
/// Base class for sound streams.
|
||||
class URHO3D_API SoundStream : public RefCounted
|
||||
{
|
||||
public:
|
||||
/// Construct.
|
||||
SoundStream();
|
||||
/// Destruct.
|
||||
~SoundStream();
|
||||
|
||||
/// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
|
||||
virtual unsigned GetData(signed char* dest, unsigned numBytes) = 0;
|
||||
|
||||
/// Set sound data format.
|
||||
void SetFormat(unsigned frequency, bool sixteenBit, bool stereo);
|
||||
/// Set whether playback should stop when no more data (GetData() returns 0 bytes.) Default false.
|
||||
void SetStopAtEnd(bool enable);
|
||||
|
||||
/// Return sample size.
|
||||
unsigned GetSampleSize() const;
|
||||
/// Return default frequency as a float.
|
||||
float GetFrequency() { return (float)frequency_; }
|
||||
/// Return default frequency as an integer.
|
||||
unsigned GetIntFrequency() { return frequency_; }
|
||||
/// Return whether playback should stop when no more data.
|
||||
bool GetStopAtEnd() const { return stopAtEnd_; }
|
||||
/// Return whether data is sixteen bit.
|
||||
bool IsSixteenBit() const { return sixteenBit_; }
|
||||
/// Return whether data is stereo.
|
||||
bool IsStereo() const { return stereo_; }
|
||||
|
||||
protected:
|
||||
/// Default frequency.
|
||||
unsigned frequency_;
|
||||
/// Stop when no more data flag.
|
||||
bool stopAtEnd_;
|
||||
/// Sixteen bit flag.
|
||||
bool sixteenBit_;
|
||||
/// Stereo flag.
|
||||
bool stereo_;
|
||||
};
|
||||
|
||||
}
|
|
@ -22,8 +22,8 @@ class Sound : public Resource
|
|||
float GetLength() const;
|
||||
unsigned GetDataSize() const;
|
||||
unsigned GetSampleSize() const;
|
||||
float GetFrequency();
|
||||
unsigned GetIntFrequency();
|
||||
float GetFrequency() const;
|
||||
unsigned GetIntFrequency() const;
|
||||
bool IsLooped() const;
|
||||
bool IsSixteenBit() const;
|
||||
bool IsStereo() const;
|
||||
|
|
|
@ -26,9 +26,6 @@ class SoundSource : public Component
|
|||
bool GetAutoRemove() const;
|
||||
bool IsPlaying() const;
|
||||
|
||||
void PlayLockless(Sound* sound);
|
||||
void StopLockless();
|
||||
|
||||
tolua_readonly tolua_property__get_set Sound* sound;
|
||||
tolua_property__get_set SoundType soundType;
|
||||
tolua_readonly tolua_property__get_set float timePosition;
|
||||
|
|
Загрузка…
Ссылка в новой задаче