diff --git a/src/common/audio/sound/i_sound.h b/src/common/audio/sound/i_sound.h index da11e9bde4..016098d48b 100644 --- a/src/common/audio/sound/i_sound.h +++ b/src/common/audio/sound/i_sound.h @@ -35,6 +35,7 @@ #ifndef __I_SOUND__ #define __I_SOUND__ +#include #include #include "i_soundinternal.h" #include "zstring.h" @@ -62,7 +63,7 @@ enum ECodecType class SoundStream { public: - virtual ~SoundStream () {} + virtual ~SoundStream() = default; enum { // For CreateStream @@ -75,11 +76,18 @@ public: Loop = 16 }; + struct Position { + uint64_t samplesplayed; + std::chrono::nanoseconds latency; + }; + virtual bool Play(bool looping, float volume) = 0; virtual void Stop() = 0; virtual void SetVolume(float volume) = 0; virtual bool SetPaused(bool paused) = 0; virtual bool IsEnded() = 0; + // Be aware this can be called during the callback invocation after Play is called. + virtual Position GetPlayPosition() = 0; virtual FString GetStats(); }; diff --git a/src/common/audio/sound/oalsound.cpp b/src/common/audio/sound/oalsound.cpp index 240b4c8ab2..dbaef18de8 100644 --- a/src/common/audio/sound/oalsound.cpp +++ b/src/common/audio/sound/oalsound.cpp @@ -183,6 +183,8 @@ class OpenALSoundStream : public SoundStream std::atomic Playing; //bool Looping; ALfloat Volume; + uint64_t Offset = 0; + std::mutex Mutex; bool SetupSource() { @@ -263,6 +265,7 @@ public: return true; /* Clear the buffer queue, then fill and queue each buffer */ + Offset = 0; alSourcei(Source, AL_BUFFER, 0); for(int i = 0;i < BufferCount;i++) { @@ -297,6 +300,7 @@ public: alSourcei(Source, AL_BUFFER, 0); getALError(); + Offset = 0; Playing.store(false); } @@ -326,6 +330,26 @@ public: return !Playing.load(); } + Position GetPlayPosition() override + { + using namespace std::chrono; + + std::lock_guard _{Mutex}; + // TODO: Get latency using AL_SOFT_source_latency + ALint state{}, offset{}, queued{}; + alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(Source, AL_SOURCE_STATE, &state); + // If the source is stopped, there was an underrun, so the play position is + // the end of the queue. + if(state == AL_STOPPED) + return Position{Offset + queued*(Data.Size()/FrameSize), nanoseconds{0}}; + // The offset is otherwise valid as long as the source has been started. + if(state != AL_INITIAL) + return Position{Offset + offset, nanoseconds{0}}; + return Position{0, nanoseconds{0}}; + } + virtual FString GetStats() { FString stats; @@ -386,8 +410,12 @@ public: // Unqueue the oldest buffer, fill it with more data, and queue it // on the end - alSourceUnqueueBuffers(Source, 1, &bufid); - processed--; + { + std::lock_guard _{Mutex}; + alSourceUnqueueBuffers(Source, 1, &bufid); + Offset += Data.Size() / FrameSize; + processed--; + } if(Callback(this, &Data[0], Data.Size(), UserData)) {