Browse Source

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.

Lasse Öörni 11 years ago
parent
commit
acb993e4ba

+ 81 - 0
Source/Engine/Audio/OggVorbisSoundStream.cpp

@@ -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;
+}
+
+}

+ 54 - 0
Source/Engine/Audio/OggVorbisSoundStream.h

@@ -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_;
+};
+
+}

+ 3 - 36
Source/Engine/Audio/Sound.cpp

@@ -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

+ 9 - 12
Source/Engine/Audio/Sound.h

@@ -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:

+ 192 - 168
Source/Engine/Audio/SoundSource.cpp

@@ -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();
+            // If stream should stop at end, do not loop after current buffer is played
+            if (soundStream_->GetStopAtEnd())
+                streamBuffer_->SetLooped(false);
+            else
+            {
+                unsigned outBytes = soundStream_->GetData(streamBuffer_->GetStart(), streamBufferSize);
+                if (outBytes)
+                {
+                    // 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
+        {
+            unsigned currentPos = position_ - streamBuffer_->GetStart();
             unsigned totalBytes;
             
             // Handle possible wraparound
-            if (currentPos >= decodePosition_)
-                totalBytes = currentPos - decodePosition_;
+            if (currentPos >= streamWritePosition_)
+                totalBytes = currentPos - streamWritePosition_;
             else
-                totalBytes = decodeBuffer_->GetDataSize() - decodePosition_ + currentPos;
+                totalBytes = streamBuffer_->GetDataSize() - streamWritePosition_ + currentPos;
             
             while (totalBytes)
             {
-                // Calculate size of current decode work unit (may need to do in two parts if wrapping)
-                unsigned bytes = decodeBuffer_->GetDataSize() - decodePosition_;
+                // 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 = 0;
-                
-                if (!eof)
-                {
-                    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;
-                        }
-                    }
-                }
-                else
+                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)
                 {
-                    memset(decodeBuffer_->GetStart() + decodePosition_, 0, bytes);
-                    outBytes = bytes;
+                    streamStopped_ = true;
+                    memset(streamBuffer_->GetStart() + streamWritePosition_ + outBytes, 0, bytes - outBytes);
                 }
                 
-                decodePosition_ += outBytes;
-                decodePosition_ %= decodeBuffer_->GetDataSize();
-                totalBytes -= outBytes;
+                streamWritePosition_ += bytes;
+                streamWritePosition_ %= streamBuffer_->GetDataSize();
+                totalBytes -= bytes;
             }
-            
-            // 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;
-            
-            // Correct initial interpolation of the looping buffer
-            decodeBuffer_->FixInterpolation();
         }
+        
+        // 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();
-}
-
 }

+ 20 - 15
Source/Engine/Audio/SoundSource.h

@@ -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_;
 };
 
 }

+ 65 - 0
Source/Engine/Audio/SoundStream.cpp

@@ -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;
+}
+
+};

+ 71 - 0
Source/Engine/Audio/SoundStream.h

@@ -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_;
+};
+
+}

+ 2 - 2
Source/Engine/LuaScript/pkgs/Audio/Sound.pkg

@@ -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;

+ 0 - 3
Source/Engine/LuaScript/pkgs/Audio/SoundSource.pkg

@@ -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;