Fixed inconsistent seek behavior in SoundStream

This commit is contained in:
Cobaltergeist 2016-07-30 11:55:39 -07:00 committed by Lukas Dürrenberger
parent 2df9abf341
commit 2207af41e4
6 changed files with 75 additions and 24 deletions

View File

@ -32,6 +32,7 @@
#include <SFML/System/NonCopyable.hpp> #include <SFML/System/NonCopyable.hpp>
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <string> #include <string>
#include <algorithm>
namespace sf namespace sf
@ -146,6 +147,22 @@ public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time getDuration() const; Time getDuration() const;
////////////////////////////////////////////////////////////
/// \brief Get the read offset of the file in time
///
/// \return Time position
///
////////////////////////////////////////////////////////////
Time getTimeOffset() const;
////////////////////////////////////////////////////////////
/// \brief Get the read offset of the file in samples
///
/// \return Sample position
///
////////////////////////////////////////////////////////////
Uint64 getSampleOffset() const;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Change the current read position to the given sample offset /// \brief Change the current read position to the given sample offset
/// ///
@ -203,6 +220,7 @@ private:
SoundFileReader* m_reader; ///< Reader that handles I/O on the file's format SoundFileReader* m_reader; ///< Reader that handles I/O on the file's format
InputStream* m_stream; ///< Input stream used to access the file's data InputStream* m_stream; ///< Input stream used to access the file's data
bool m_streamOwned; ///< Is the stream internal or external? bool m_streamOwned; ///< Is the stream internal or external?
Uint64 m_sampleOffset; ///< Sample Read Position
Uint64 m_sampleCount; ///< Total number of samples in the file Uint64 m_sampleCount; ///< Total number of samples in the file
unsigned int m_channelCount; ///< Number of channels of the sound unsigned int m_channelCount; ///< Number of channels of the sound
unsigned int m_sampleRate; ///< Number of samples per second unsigned int m_sampleRate; ///< Number of samples per second

View File

@ -169,7 +169,6 @@ private:
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
InputSoundFile m_file; ///< The streamed music file InputSoundFile m_file; ///< The streamed music file
Time m_duration; ///< Music duration
std::vector<Int16> m_samples; ///< Temporary buffer of samples std::vector<Int16> m_samples; ///< Temporary buffer of samples
Mutex m_mutex; ///< Mutex protecting the data Mutex m_mutex; ///< Mutex protecting the data
}; };

View File

@ -254,11 +254,12 @@ private:
/// playing queue. /// playing queue.
/// ///
/// \param bufferNum Number of the buffer to fill (in [0, BufferCount]) /// \param bufferNum Number of the buffer to fill (in [0, BufferCount])
/// \param immediateLoop Treat empty buffers as spent, and act on loops immediately
/// ///
/// \return True if the stream source has requested to stop, false otherwise /// \return True if the stream source has requested to stop, false otherwise
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool fillAndPushBuffer(unsigned int bufferNum); bool fillAndPushBuffer(unsigned int bufferNum, bool immediateLoop = false);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Fill the audio buffers and put them all into the playing queue /// \brief Fill the audio buffers and put them all into the playing queue

View File

@ -41,6 +41,7 @@ InputSoundFile::InputSoundFile() :
m_reader (NULL), m_reader (NULL),
m_stream (NULL), m_stream (NULL),
m_streamOwned (false), m_streamOwned (false),
m_sampleOffset (0),
m_sampleCount (0), m_sampleCount (0),
m_channelCount(0), m_channelCount(0),
m_sampleRate (0) m_sampleRate (0)
@ -195,15 +196,42 @@ unsigned int InputSoundFile::getSampleRate() const
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time InputSoundFile::getDuration() const Time InputSoundFile::getDuration() const
{ {
// Make sure we don't divide by 0
if (m_channelCount == 0 || m_sampleRate == 0)
return Time::Zero;
return seconds(static_cast<float>(m_sampleCount) / m_channelCount / m_sampleRate); return seconds(static_cast<float>(m_sampleCount) / m_channelCount / m_sampleRate);
} }
////////////////////////////////////////////////////////////
Time InputSoundFile::getTimeOffset() const
{
// Make sure we don't divide by 0
if (m_channelCount == 0 || m_sampleRate == 0)
return Time::Zero;
return seconds(static_cast<float>(m_sampleOffset) / m_channelCount / m_sampleRate);
}
////////////////////////////////////////////////////////////
Uint64 InputSoundFile::getSampleOffset() const
{
return m_sampleOffset;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void InputSoundFile::seek(Uint64 sampleOffset) void InputSoundFile::seek(Uint64 sampleOffset)
{ {
if (m_reader) if (m_reader)
m_reader->seek(sampleOffset); {
// The reader handles an overrun gracefully, but we
// pre-check to keep our known position consistent
m_sampleOffset = std::min(sampleOffset, m_sampleCount);
m_reader->seek(m_sampleOffset);
}
} }
@ -217,10 +245,11 @@ void InputSoundFile::seek(Time timeOffset)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Uint64 InputSoundFile::read(Int16* samples, Uint64 maxCount) Uint64 InputSoundFile::read(Int16* samples, Uint64 maxCount)
{ {
Uint64 readSamples = 0;
if (m_reader && samples && maxCount) if (m_reader && samples && maxCount)
return m_reader->read(samples, maxCount); readSamples = m_reader->read(samples, maxCount);
else m_sampleOffset += readSamples;
return 0; return readSamples;
} }
@ -238,6 +267,7 @@ void InputSoundFile::close()
m_streamOwned = false; m_streamOwned = false;
} }
m_stream = NULL; m_stream = NULL;
m_sampleOffset = 0;
// Reset the sound file attributes // Reset the sound file attributes
m_sampleCount = 0; m_sampleCount = 0;

View File

@ -36,8 +36,7 @@ namespace sf
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Music::Music() : Music::Music() :
m_file (), m_file ()
m_duration()
{ {
} }
@ -105,7 +104,7 @@ bool Music::openFromStream(InputStream& stream)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time Music::getDuration() const Time Music::getDuration() const
{ {
return m_duration; return m_file.getDuration();
} }
@ -118,8 +117,8 @@ bool Music::onGetData(SoundStream::Chunk& data)
data.samples = &m_samples[0]; data.samples = &m_samples[0];
data.sampleCount = static_cast<std::size_t>(m_file.read(&m_samples[0], m_samples.size())); data.sampleCount = static_cast<std::size_t>(m_file.read(&m_samples[0], m_samples.size()));
// Check if we have reached the end of the audio file // Check if we have stopped obtaining samples or reached the end of the audio file
return data.sampleCount == m_samples.size(); return (data.sampleCount != 0) && (m_file.getSampleOffset() < m_file.getSampleCount());
} }
@ -135,9 +134,6 @@ void Music::onSeek(Time timeOffset)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void Music::initialize() void Music::initialize()
{ {
// Compute the music duration
m_duration = m_file.getDuration();
// Resize the internal buffer so that it can contain 1 second of audio samples // Resize the internal buffer so that it can contain 1 second of audio samples
m_samples.resize(m_file.getSampleRate() * m_file.getChannelCount()); m_samples.resize(m_file.getSampleRate() * m_file.getChannelCount());

View File

@ -78,6 +78,8 @@ void SoundStream::initialize(unsigned int channelCount, unsigned int sampleRate)
{ {
m_channelCount = channelCount; m_channelCount = channelCount;
m_sampleRate = sampleRate; m_sampleRate = sampleRate;
m_samplesProcessed = 0;
m_isStreaming = false;
// Deduce the format from the number of channels // Deduce the format from the number of channels
m_format = priv::AudioDevice::getFormatFromChannelCount(channelCount); m_format = priv::AudioDevice::getFormatFromChannelCount(channelCount);
@ -127,11 +129,7 @@ void SoundStream::play()
stop(); stop();
} }
// Move to the beginning
onSeek(Time::Zero);
// Start updating the stream in a separate thread to avoid blocking the application // Start updating the stream in a separate thread to avoid blocking the application
m_samplesProcessed = 0;
m_isStreaming = true; m_isStreaming = true;
m_threadStartState = Playing; m_threadStartState = Playing;
m_thread.launch(); m_thread.launch();
@ -397,7 +395,7 @@ void SoundStream::streamData()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundStream::fillAndPushBuffer(unsigned int bufferNum) bool SoundStream::fillAndPushBuffer(unsigned int bufferNum, bool immediateLoop)
{ {
bool requestStop = false; bool requestStop = false;
@ -417,6 +415,13 @@ bool SoundStream::fillAndPushBuffer(unsigned int bufferNum)
// If we previously had no data, try to fill the buffer once again // If we previously had no data, try to fill the buffer once again
if (!data.samples || (data.sampleCount == 0)) if (!data.samples || (data.sampleCount == 0))
{ {
// If immediateLoop is specified, we have to immediately adjust the sample count
if (immediateLoop)
{
// We just tried to begin preloading at EOF: reset the sample count
m_samplesProcessed = 0;
m_endBuffers[bufferNum] = false;
}
return fillAndPushBuffer(bufferNum); return fillAndPushBuffer(bufferNum);
} }
} }
@ -451,7 +456,9 @@ bool SoundStream::fillQueue()
bool requestStop = false; bool requestStop = false;
for (int i = 0; (i < BufferCount) && !requestStop; ++i) for (int i = 0; (i < BufferCount) && !requestStop; ++i)
{ {
if (fillAndPushBuffer(i)) // Since no sound has been loaded yet, we can't schedule loop seeks preemptively,
// So if we start on EOF or Loop End, we let fillAndPushBuffer() adjust the sample count
if (fillAndPushBuffer(i, (i == 0)))
requestStop = true; requestStop = true;
} }