Gapless playback seems to be working with waveout! Not perfect for all

MP3 files, but that's to be expected. TODO: CoreAudio changes to allow
gapless playback.
This commit is contained in:
casey 2016-06-01 21:21:35 -07:00
parent 6ec0a8042b
commit a8f8425f91
14 changed files with 452 additions and 475 deletions

View File

@ -183,10 +183,10 @@ bool Mpg123Decoder::Open(IDataStream *fileStream){
this->fileStream = fileStream;
if (mpg123_open_feed(this->decoder) == MPG123_OK) {
mpg123_param(
int result = mpg123_param(
this->decoder,
MPG123_ADD_FLAGS,
MPG123_FUZZY | MPG123_SEEKBUFFER,
MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS | MPG123_QUIET,
0);
mpg123_set_filesize(this->decoder, this->fileStream->Length());

View File

@ -1,34 +1,34 @@
#pragma once
#include <core/sdk/IDecoder.h>
#include <core/sdk/IDataStream.h>
#pragma once
#include <core/sdk/IDecoder.h>
#include <core/sdk/IDataStream.h>
#ifdef _MSC_VER /* ehh */
typedef long ssize_t;
#endif
#include <mpg123.h>
class Mpg123Decoder : public musik::core::audio::IDecoder {
public:
Mpg123Decoder();
virtual ~Mpg123Decoder();
virtual bool Open(musik::core::io::IDataStream *dataStream);
virtual double SetPosition(double seconds);
virtual bool GetBuffer(musik::core::audio::IBuffer *buffer);
virtual void Destroy();
private:
bool Feed();
private:
musik::core::io::IDataStream *fileStream;
mpg123_handle *decoder;
unsigned long cachedLength;
long sampleRate;
int channels;
int sampleSizeBytes;
int lastMpg123Status;
};
#endif
#include <mpg123.h>
class Mpg123Decoder : public musik::core::audio::IDecoder {
public:
Mpg123Decoder();
virtual ~Mpg123Decoder();
virtual bool Open(musik::core::io::IDataStream *dataStream);
virtual double SetPosition(double seconds);
virtual bool GetBuffer(musik::core::audio::IBuffer *buffer);
virtual void Destroy();
private:
bool Feed();
private:
musik::core::io::IDataStream *fileStream;
mpg123_handle *decoder;
unsigned long cachedLength;
long sampleRate;
int channels;
int sampleSizeBytes;
int lastMpg123Status;
};

View File

@ -104,10 +104,12 @@
<ClCompile Include="stdafx.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\fmt123.h" />
<ClInclude Include="include\mpg123.h" />
<ClInclude Include="include\out123.h" />
<ClInclude Include="Mpg123Decoder.h" />
<ClInclude Include="Mpg123DecoderFactory.h" />
<ClInclude Include="mpg123\src\config.h" />
<ClInclude Include="mpg123\src\libmpg123\mpg123.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="stdafx.h" />
</ItemGroup>

View File

@ -28,7 +28,6 @@
<Filter>plugin</Filter>
</ClInclude>
<ClInclude Include="mpg123\src\config.h" />
<ClInclude Include="mpg123\src\libmpg123\mpg123.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Mpg123DecoderFactory.h">
<Filter>plugin</Filter>
@ -36,5 +35,8 @@
<ClInclude Include="Mpg123Decoder.h">
<Filter>plugin</Filter>
</ClInclude>
<ClInclude Include="include\mpg123.h" />
<ClInclude Include="include\out123.h" />
<ClInclude Include="include\fmt123.h" />
</ItemGroup>
</Project>

View File

@ -34,7 +34,7 @@
#include "WaveOut.h"
#define MAX_VOLUME 0xFFFF
#define MAX_BUFFERS 8
#define MAX_BUFFERS_PER_OUTPUT 8
static void notifyBufferProcessed(WaveOutBuffer *buffer) {
/* let the provider know the output device is done with the buffer; the
@ -49,6 +49,7 @@ WaveOut::WaveOut()
, currentVolume(1.0)
, threadId(0)
, threadHandle(NULL)
, playing(false)
{
}
@ -75,20 +76,25 @@ void WaveOut::Destroy() {
}
}
{
boost::recursive_mutex::scoped_lock lock1(this->bufferQueueMutex);
this->ClearBufferQueue();
}
this->ClearBufferQueue();
delete this;
}
void WaveOut::Pause() {
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
waveOutPause(this->waveHandle);
this->playing = false;
}
void WaveOut::Resume() {
waveOutRestart(this->waveHandle);
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
if (!this->playing) {
waveOutRestart(this->waveHandle);
}
this->playing = true;
}
void WaveOut::SetVolume(double volume) {
@ -102,51 +108,56 @@ void WaveOut::SetVolume(double volume) {
}
void WaveOut::Stop() {
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
{
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
if (this->waveHandle != NULL) {
waveOutReset(this->waveHandle);
}
}
{
boost::recursive_mutex::scoped_lock lock1(this->bufferQueueMutex);
this->ClearBufferQueue();
if (this->waveHandle != NULL) {
waveOutReset(this->waveHandle);
}
}
void WaveOut::ClearBufferQueue() {
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
std::list<WaveOutBufferPtr> remove;
{
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
std::swap(this->queuedBuffers, remove);
}
/* notify and free any pending buffers, the Player in the core
will be waiting for all pending buffers to be processed. */
if (this->queuedBuffers.size() > 0) {
BufferList::iterator it = this->queuedBuffers.begin();
for (; it != this->queuedBuffers.end(); it++) {
if (remove.size() > 0) {
BufferList::iterator it = remove.begin();
for (; it != remove.end(); it++) {
notifyBufferProcessed((*it).get());
}
this->queuedBuffers.clear();
}
}
void WaveOut::OnBufferWrittenToOutput(WaveOutBuffer *buffer) {
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
/* removed the buffer. it should be at the front of the queue. */
BufferList::iterator it = this->queuedBuffers.begin();
for( ; it != this->queuedBuffers.end(); it++) {
if (it->get() == buffer) {
notifyBufferProcessed(buffer);
this->queuedBuffers.erase(it);
return;
WaveOutBufferPtr erased;
{
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
/* removed the buffer. it should be at the front of the queue. */
BufferList::iterator it = this->queuedBuffers.begin();
for (; it != this->queuedBuffers.end(); it++) {
if (it->get() == buffer) {
erased = *it;
this->queuedBuffers.erase(it);
break;
}
}
}
if (erased) {
notifyBufferProcessed(erased.get());
}
}
void WaveOut::StartWaveOutThread() {
this->StopWaveOutThread();
this->threadHandle = CreateThread(
NULL,
0,
@ -168,11 +179,9 @@ void WaveOut::StopWaveOutThread() {
bool WaveOut::Play(IBuffer *buffer, IBufferProvider *provider) {
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
size_t bufferCount = bufferCount = this->queuedBuffers.size();
/* if we have a different format, return false and wait for the pending
buffers to be written to the output device. */
if (bufferCount > 0) {
if (!this->queuedBuffers.empty()) {
bool formatChanged =
this->currentChannels != buffer->Channels() ||
this->currentSampleRate != buffer->SampleRate();
@ -182,7 +191,24 @@ bool WaveOut::Play(IBuffer *buffer, IBufferProvider *provider) {
}
}
if (MAX_BUFFERS > bufferCount) {
size_t buffersForOutput = 0;
auto it = this->queuedBuffers.begin();
while (it != this->queuedBuffers.end()) {
if ((*it)->GetBufferProvider() == provider) {
++buffersForOutput;
}
++it;
}
if (MAX_BUFFERS_PER_OUTPUT > buffersForOutput) {
{
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
if (!this->playing) {
return false;
}
}
/* ensure the output device itself (the WAVEOUT) is configured correctly
for the new buffer */
this->SetFormat(buffer);
@ -211,7 +237,6 @@ void WaveOut::SetFormat(IBuffer *buffer) {
this->currentSampleRate = buffer->SampleRate();
this->Stop();
this->StopWaveOutThread();
this->StartWaveOutThread();
/* reset, and configure speaker output */

View File

@ -65,6 +65,7 @@ class WaveOut : public IOutput {
void SetFormat(IBuffer *buffer);
void StartWaveOutThread();
void StopWaveOutThread();
void ResetWaveOut();
void ClearBufferQueue();
protected:
@ -86,6 +87,7 @@ class WaveOut : public IOutput {
int currentChannels;
long currentSampleRate;
double currentVolume;
bool playing;
/* a queue of buffers we've recieved from the core Player, and have enqueued
to the output device. we need to notify the IBufferProvider when they have finished

View File

@ -1,164 +1,168 @@
//////////////////////////////////////////////////////////////////////////////
// Copyright <20> 2007, Daniel <20>nnerby
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include <core/audio/Buffer.h>
#define DEBUG 0
#if DEBUG > 0
#include <iostream>
#endif
using namespace musik::core::audio;
Buffer::Buffer(void)
:buffer(NULL)
,sampleSize(0)
,internalBufferSize(0)
,sampleRate(44100)
,channels(2) {
}
Buffer::~Buffer() {
delete this->buffer;
}
BufferPtr Buffer::Create() {
return BufferPtr(new Buffer());
}
//////////////////////////////////////////////////////////////////////////////
// Copyright <20> 2007, Daniel <20>nnerby
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include <core/audio/Buffer.h>
#define DEBUG 0
#if DEBUG > 0
#include <iostream>
#endif
using namespace musik::core::audio;
Buffer::Buffer(void)
:buffer(NULL)
,sampleSize(0)
,internalBufferSize(0)
,sampleRate(44100)
,channels(2) {
}
Buffer::~Buffer() {
delete this->buffer;
}
BufferPtr Buffer::Create() {
return BufferPtr(new Buffer());
}
long Buffer::SampleRate() const { /* hertz */
return this->sampleRate;
}
void Buffer::SetSampleRate(long sampleRate) { /* hertz */
return this->sampleRate;
}
void Buffer::SetSampleRate(long sampleRate) { /* hertz */
this->sampleRate = sampleRate;
}
int Buffer::Channels() const {
return this->channels;
}
void Buffer::SetChannels(int channels) {
this->channels = channels;
this->ResizeBuffer();
}
float* Buffer::BufferPointer() const {
return this->buffer;
}
}
int Buffer::Channels() const {
return this->channels;
}
void Buffer::SetChannels(int channels) {
this->channels = channels;
this->ResizeBuffer();
}
float* Buffer::BufferPointer() const {
return this->buffer;
}
long Buffer::Samples() const { /* pre-multiplied by channel count */
return this->sampleSize;
}
void Buffer::SetSamples(long samples) {
this->sampleSize = samples;
this->ResizeBuffer();
}
void Buffer::CopyFormat(BufferPtr fromBuffer) {
this->sampleSize = fromBuffer->Samples();
this->channels = fromBuffer->Channels();
this->sampleRate = fromBuffer->SampleRate();
this->ResizeBuffer();
}
void Buffer::ResizeBuffer() {
long requiredBufferSize = this->sampleSize * this->channels;
if (requiredBufferSize > this->internalBufferSize) {
if (this->buffer) {
delete this->buffer;
this->buffer = NULL;
}
this->buffer = new float[requiredBufferSize];
this->internalBufferSize = requiredBufferSize;
}
}
}
void Buffer::SetSamples(long samples) {
this->sampleSize = samples;
this->ResizeBuffer();
}
void Buffer::CopyFormat(BufferPtr fromBuffer) {
this->sampleSize = fromBuffer->Samples();
this->channels = fromBuffer->Channels();
this->sampleRate = fromBuffer->SampleRate();
this->ResizeBuffer();
}
void Buffer::ResizeBuffer() {
long requiredBufferSize = this->sampleSize * this->channels;
if (requiredBufferSize > this->internalBufferSize) {
if (this->buffer) {
delete this->buffer;
this->buffer = NULL;
}
this->buffer = new float[requiredBufferSize];
this->internalBufferSize = requiredBufferSize;
}
}
/* logical bytes; backing store may be be larger */
long Buffer::Bytes() const {
return sizeof(float) * this->sampleSize * this->channels;
}
double Buffer::Position() const { /* position in track. ???? */
return sizeof(float) * this->sampleSize * this->channels;
}
double Buffer::Position() const {
return this->position;
}
bool Buffer::Append(BufferPtr appendBuffer) {
if (this->SampleRate() == appendBuffer->SampleRate() &&
this->Channels() == appendBuffer->Channels())
{
/* number of floats (not bytes) in buffer */
long newBufferSize = (this->Samples() + appendBuffer->Samples()) * this->channels;
if (newBufferSize > this->internalBufferSize) { /* resize, then copy, if too small */
float *newBuffer = new float[newBufferSize];
CopyFloat(newBuffer, this->buffer, this->sampleSize * this->channels);
float *dst = &newBuffer[this->sampleSize * this->channels];
float *src = appendBuffer->BufferPointer();
long count = appendBuffer->Samples() * this->channels;
CopyFloat(dst, src, count);
delete this->buffer;
#if DEBUG > 0
std::cerr << "resized with realloc old: " << this->internalBufferSize << " new: " << newBufferSize << "\n";
#endif
this->buffer = newBuffer;
this->internalBufferSize = newBufferSize;
}
else { /* append, no resize required */
float *dst = &this->buffer[this->sampleSize * this->channels];
float *src = appendBuffer->BufferPointer();
long count = appendBuffer->Samples() * this->channels;
CopyFloat(dst, src, count);
#if DEBUG > 0
std::cerr << "appended " << count << " floats to existing buffer, logical bytes=" << newBufferSize << "\n";
#endif
}
this->sampleSize = newBufferSize / this->channels;
return true;
}
return false;
}
}
void Buffer::SetPosition(double position) {
this->position = position;
}
bool Buffer::Append(BufferPtr appendBuffer) {
if (this->SampleRate() == appendBuffer->SampleRate() &&
this->Channels() == appendBuffer->Channels())
{
/* number of floats (not bytes) in buffer */
long newBufferSize = (this->Samples() + appendBuffer->Samples()) * this->channels;
if (newBufferSize > this->internalBufferSize) { /* resize, then copy, if too small */
float *newBuffer = new float[newBufferSize];
CopyFloat(newBuffer, this->buffer, this->sampleSize * this->channels);
float *dst = &newBuffer[this->sampleSize * this->channels];
float *src = appendBuffer->BufferPointer();
long count = appendBuffer->Samples() * this->channels;
CopyFloat(dst, src, count);
delete this->buffer;
#if DEBUG > 0
std::cerr << "resized with realloc old: " << this->internalBufferSize << " new: " << newBufferSize << "\n";
#endif
this->buffer = newBuffer;
this->internalBufferSize = newBufferSize;
}
else { /* append, no resize required */
float *dst = &this->buffer[this->sampleSize * this->channels];
float *src = appendBuffer->BufferPointer();
long count = appendBuffer->Samples() * this->channels;
CopyFloat(dst, src, count);
#if DEBUG > 0
std::cerr << "appended " << count << " floats to existing buffer, logical bytes=" << newBufferSize << "\n";
#endif
}
this->sampleSize = newBufferSize / this->channels;
return true;
}
return false;
}

View File

@ -1,81 +1,78 @@
//////////////////////////////////////////////////////////////////////////////
// Copyright <20> 2007, Daniel <20>nnerby
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/config.h>
#include <boost/shared_ptr.hpp>
#include <core/sdk/IBuffer.h>
namespace musik { namespace core { namespace audio {
class Buffer;
class Stream;
typedef std::shared_ptr<Buffer> BufferPtr;
//////////////////////////////////////////////////////////////////////////////
// Copyright <20> 2007, Daniel <20>nnerby
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/config.h>
#include <boost/shared_ptr.hpp>
#include <core/sdk/IBuffer.h>
namespace musik { namespace core { namespace audio {
class Buffer;
class Stream;
typedef std::shared_ptr<Buffer> BufferPtr;
class Buffer : public IBuffer {
private:
Buffer();
public:
static BufferPtr Create();
~Buffer();
virtual long SampleRate() const;
virtual void SetSampleRate(long sampleRate);
virtual int Channels() const;
virtual void SetChannels(int channels);
virtual float* BufferPointer() const;
virtual long Samples() const;
virtual void SetSamples(long samples);
virtual long Bytes() const;
virtual double Position() const;
bool Append(BufferPtr appendBuffer);
void CopyFormat(BufferPtr fromBuffer);
private:
void ResizeBuffer();
private:
float *buffer;
long sampleSize;
long internalBufferSize;
long sampleRate;
int channels;
protected:
friend class Stream;
double position;
};
} } }
private:
Buffer();
public:
static BufferPtr Create();
~Buffer();
virtual long SampleRate() const;
virtual void SetSampleRate(long sampleRate);
virtual int Channels() const;
virtual void SetChannels(int channels);
virtual float* BufferPointer() const;
virtual long Samples() const;
virtual void SetSamples(long samples);
virtual long Bytes() const;
virtual double Position() const;
void SetPosition(double position);
bool Append(BufferPtr appendBuffer);
void CopyFormat(BufferPtr fromBuffer);
private:
void ResizeBuffer();
float *buffer;
long sampleSize;
long internalBufferSize;
long sampleRate;
int channels;
double position;
};
} } }

View File

@ -46,11 +46,11 @@ using std::max;
static std::string TAG = "Player";
PlayerPtr Player::Create(const std::string &url, double volume, OutputPtr output) {
return PlayerPtr(new Player(url, volume, output));
PlayerPtr Player::Create(const std::string &url, OutputPtr output) {
return PlayerPtr(new Player(url, output));
}
Player::OutputPtr Player::CreateDefaultOutput() {
OutputPtr Player::CreateDefaultOutput() {
/* if no output is specified, find all output plugins, and select the first one. */
typedef std::vector<OutputPtr> OutputVector;
@ -62,32 +62,36 @@ Player::OutputPtr Player::CreateDefaultOutput() {
return outputs.front();
}
return Player::OutputPtr();
return OutputPtr();
}
Player::Player(const std::string &url, double volume, OutputPtr output)
: volume(volume)
, state(Player::Precache)
Player::Player(const std::string &url, OutputPtr output)
: state(Player::Precache)
, url(url)
, currentPosition(0)
, output(output)
, setPosition(-1) {
musik::debug::info(TAG, "new instance created");
/* we allow callers to specify an output device; but if they don't,
we will create and manage our own. */
this->output = output ? output : Player::CreateDefaultOutput();
if (!this->output) {
throw std::runtime_error("output cannot be null!");
}
/* each player instance is driven by a background thread. start it. */
this->thread.reset(new boost::thread(boost::bind(&Player::ThreadLoop,this)));
}
Player::~Player() {
this->Stop();
if (this->thread) {
this->thread->join();
{
boost::mutex::scoped_lock lock(this->mutex);
this->state = Player::Quit;
this->prebufferQueue.clear();
this->writeToOutputCondition.notify_all();
}
this->thread->join();
}
void Player::Play() {
@ -97,28 +101,9 @@ void Player::Play() {
}
void Player::Stop() {
{
boost::mutex::scoped_lock lock(this->mutex);
this->state = Player::Quit;
this->prebufferQueue.clear();
this->writeToOutputCondition.notify_all();
}
if (this->output) {
this->output->Stop();
}
}
void Player::Pause() {
if (this->output) {
this->output->Pause();
}
}
void Player::Resume() {
if (this->output) {
this->output->Resume();
}
boost::mutex::scoped_lock lock(this->mutex);
this->state = Player::Quit;
this->writeToOutputCondition.notify_all();
}
double Player::Position() {
@ -131,21 +116,6 @@ void Player::SetPosition(double seconds) {
this->setPosition = std::max(0.0, seconds);
}
double Player::Volume() {
boost::mutex::scoped_lock lock(this->mutex);
return this->volume;
}
void Player::SetVolume(double volume) {
boost::mutex::scoped_lock lock(this->mutex);
this->volume = volume;
if (this->output) {
this->output->SetVolume(this->volume);
}
}
int Player::State() {
boost::mutex::scoped_lock lock(this->mutex);
return this->state;
@ -155,42 +125,35 @@ void Player::ThreadLoop() {
/* create and open the stream */
this->stream = Stream::Create();
if (this->stream->OpenStream(this->url)) {
/* ensure the volume is set properly */
{
boost::mutex::scoped_lock lock(this->mutex);
this->output->SetVolume(this->volume);
}
/* precache until buffers are full */
bool keepPrecaching = true;
while (this->State() == Precache && keepPrecaching) {
keepPrecaching = this->PreBuffer();
boost::thread::yield();
}
/* wait until we enter the Playing or Quit state; we may still
be in the Precache state. */
{
while (this->state == Precache) {
boost::mutex::scoped_lock lock(this->mutex);
while (this->state == Precache) {
this->writeToOutputCondition.wait(lock);
}
this->writeToOutputCondition.wait(lock);
}
this->PlaybackStarted(this);
/* we're ready to go.... */
bool finished = false;
BufferPtr buffer;
bool startedPlaying = false;
while (!finished && !this->Exited()) {
/* see if we've been asked to seek since the last sample was
played. if we have, clear our output buffer and seek the
stream. */
if (this->setPosition != -1) {
this->output->Stop();
this->output->Stop(); /* flush all buffers */
this->output->Resume(); /* start it back up */
while (this->lockedBuffers.size() > 0) {
boost::mutex::scoped_lock lock(this->mutex);
writeToOutputCondition.wait(this->mutex);
}
@ -224,6 +187,11 @@ void Player::ThreadLoop() {
the output device. */
if (buffer) {
if (this->output->Play(buffer.get(), this)) {
if (!startedPlaying) {
startedPlaying = true;
this->PlaybackStarted(this);
}
/* success! the buffer was accepted by the output.*/
boost::mutex::scoped_lock lock(this->mutex);
@ -263,37 +231,18 @@ void Player::ThreadLoop() {
this->PlaybackError(this);
}
bool stopped = false;
this->state = Player::Quit;
/* wait until all remaining buffers have been written, set final state... */
{
boost::mutex::scoped_lock lock(this->mutex);
/* if the state is "Quit" that means the user terminated the stream, so no
more buffers will be played / recycled from the output device. kill them now.*/
if (this->state == Player::Quit) {
this->lockedBuffers.clear();
while (this->lockedBuffers.size() > 0) {
writeToOutputCondition.wait(this->mutex);
}
/* otherwise wait for their completion */
else {
while (this->lockedBuffers.size() > 0) {
writeToOutputCondition.wait(this->mutex);
}
}
stopped = (this->state == Player::Quit);
this->state = Player::Quit;
}
if (stopped) {
this->PlaybackStopped(this);
}
else {
this->PlaybackFinished(this);
}
this->output.reset();
this->stream.reset();
this->PlaybackFinished(this);
}
void Player::ReleaseAllBuffers() {

View File

@ -49,21 +49,20 @@ namespace musik { namespace core { namespace audio {
class Player;
typedef std::shared_ptr<Player> PlayerPtr;
class Output;
typedef std::shared_ptr<IOutput> OutputPtr;
class Player : public IBufferProvider {
public:
typedef std::shared_ptr<IOutput> OutputPtr;
static OutputPtr CreateDefaultOutput();
static PlayerPtr Create(
const std::string &url,
double volume = 1.0f,
OutputPtr output = OutputPtr());
OutputPtr output);
Player(
const std::string &url,
double volume = 1.0f,
OutputPtr output = OutputPtr());
OutputPtr output);
~Player();
@ -71,25 +70,18 @@ namespace musik { namespace core { namespace audio {
void Play();
void Stop();
void Pause();
void Resume();
double Position();
void SetPosition(double seconds);
double Volume();
void SetVolume(double volume);
std::string GetUrl() const { return this->url; }
bool Exited();
public:
typedef sigslot::signal1<Player*> PlayerEvent;
PlayerEvent PlaybackStarted;
PlayerEvent PlaybackAlmostEnded;
PlayerEvent PlaybackFinished;
PlayerEvent PlaybackStopped;
PlayerEvent PlaybackError;
private:

View File

@ -145,7 +145,9 @@ BufferPtr Stream::GetNextBufferFromDecoder() {
this->decoderSamplePosition += buffer->Samples();
/* calculate the position (seconds) in the buffer */
buffer->position = ((double) this->decoderSamplePosition) / ((double) this->decoderSampleRate);
buffer->SetPosition(
((double) this->decoderSamplePosition) /
((double) this->decoderSampleRate));
return buffer;
}
@ -175,7 +177,7 @@ BufferPtr Stream::GetNextProcessedOutputBuffer() {
for (Dsps::iterator dsp = this->dsps.begin(); dsp != this->dsps.end(); ++dsp) {
oldBuffer->CopyFormat(currentBuffer);
oldBuffer->position = currentBuffer->position;
currentBuffer->SetPosition(oldBuffer->Position());
if ((*dsp)->Process(currentBuffer.get(), oldBuffer.get())) {
currentBuffer.swap(oldBuffer);

View File

@ -53,14 +53,6 @@ static std::string TAG = "Transport";
thread.detach(); \
}
static void pausePlayer(Player* p) {
p->Pause();
}
static void resumePlayer(Player* p) {
p->Resume();
}
static void stopPlayer(Player* p) {
p->Stop();
}
@ -72,7 +64,8 @@ static void deletePlayer(Player* p) {
Transport::Transport()
: volume(1.0)
, state(PlaybackStopped)
, nextPlayer(NULL) {
, nextPlayer(NULL)
, nextCanStart(false) {
this->output = Player::CreateDefaultOutput();
}
@ -88,14 +81,10 @@ void Transport::PrepareNextTrack(const std::string& trackUrl) {
bool startNext = false;
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
this->nextPlayer = new Player(trackUrl, this->volume, this->output);
startNext = this->state == PlaybackStopped;
this->nextPlayer = new Player(trackUrl, this->output);
startNext = this->nextCanStart;
}
/* we raise an event when the current stream has almost finished, which
allows the calling app to prepare a new track. by the time this preparation
is complete, the track may have ended. if we're in the stopped state, start
playback of this new track immediately. */
if (startNext) {
this->StartWithPlayer(this->nextPlayer);
}
@ -104,7 +93,7 @@ void Transport::PrepareNextTrack(const std::string& trackUrl) {
void Transport::Start(const std::string& url) {
musik::debug::info(TAG, "we were asked to start the track at " + url);
Player* newPlayer = new Player(url, this->volume, this->output);
Player* newPlayer = new Player(url, this->output);
musik::debug::info(TAG, "Player created successfully");
this->StartWithPlayer(newPlayer);
@ -115,23 +104,25 @@ void Transport::StartWithPlayer(Player* newPlayer) {
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
bool playingNext = (newPlayer == nextPlayer);
if (newPlayer != nextPlayer) {
delete nextPlayer;
}
this->nextPlayer = NULL;
this->Stop(true); /* true = suppress stopped event */
this->Stop(playingNext); /* true = suppress stopped event */
this->SetNextCanStart(false);
newPlayer->PlaybackStarted.connect(this, &Transport::OnPlaybackStarted);
newPlayer->PlaybackAlmostEnded.connect(this, &Transport::OnPlaybackAlmostEnded);
newPlayer->PlaybackFinished.connect(this, &Transport::OnPlaybackFinished);
newPlayer->PlaybackStopped.connect(this, &Transport::OnPlaybackStopped);
newPlayer->PlaybackError.connect(this, &Transport::OnPlaybackError);
musik::debug::info(TAG, "play()");
this->active.push_front(newPlayer);
newPlayer->SetVolume(this->volume);
this->output->Resume();
newPlayer->Play();
}
@ -143,7 +134,7 @@ void Transport::Stop() {
this->Stop(false);
}
void Transport::Stop(bool suppressEvent) {
void Transport::Stop(bool playingNext) {
musik::debug::info(TAG, "stop");
std::list<Player*> toDelete;
@ -156,15 +147,20 @@ void Transport::Stop(bool suppressEvent) {
/* delete these in the background to avoid deadlock in some cases
where this method is implicitly triggered via Player callback. however,
it's perfectly safe (and actually required) to stop them immediately to
ensure all pending buffers have been flushed. */
we should stop them immediately so they stop producing audio. */
std::for_each(toDelete.begin(), toDelete.end(), stopPlayer);
DEFER(&Transport::DeletePlayers, toDelete);
/* stopping the transport will stop any buffers that are currently in
flight. this makes the sound end immediately. */
if (!playingNext) {
this->output->Stop();
}
/* if we know we're starting another track immediately, suppress
the stop event. this functionality is not available to the public
interface, it's an internal optimization */
if (!suppressEvent) {
if (!playingNext) {
this->SetPlaybackState(PlaybackStopped);
}
}
@ -174,9 +170,10 @@ bool Transport::Pause() {
size_t count = 0;
this->output->Pause();
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
std::for_each(this->active.begin(), this->active.end(), pausePlayer);
count = this->active.size();
}
@ -191,12 +188,19 @@ bool Transport::Pause() {
bool Transport::Resume() {
musik::debug::info(TAG, "resume");
this->output->Resume();
size_t count = 0;
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
std::for_each(this->active.begin(), this->active.end(), resumePlayer);
count = this->active.size();
auto it = this->active.begin();
while (it != this->active.end()) {
(*it)->Play();
++it;
}
}
if (count) {
@ -241,25 +245,12 @@ void Transport::SetVolume(double volume) {
this->VolumeChanged();
}
musik::debug::info(TAG, boost::str(
boost::format("set volume %d%%") % round(volume * 100)));
std::string output = boost::str(
boost::format("set volume %d%%") % round(volume * 100));
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
musik::debug::info(TAG, output);
if (!this->active.empty()) {
this->active.front()->SetVolume(volume);
}
}
}
void Transport::OnPlaybackStarted(Player* player) {
this->RaiseStreamEvent(Transport::StreamPlaying, player);
this->SetPlaybackState(Transport::PlaybackPlaying);
}
void Transport::OnPlaybackAlmostEnded(Player* player) {
this->RaiseStreamEvent(Transport::StreamAlmostDone, player);
this->output->SetVolume(this->volume);
}
void Transport::RemoveActive(Player* player) {
@ -287,7 +278,33 @@ void Transport::DeletePlayers(std::list<Player*> players) {
std::for_each(players.begin(), players.end(), deletePlayer);
}
void Transport::SetNextCanStart(bool nextCanStart) {
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
this->nextCanStart = nextCanStart;
}
void Transport::OnPlaybackStarted(Player* player) {
this->RaiseStreamEvent(Transport::StreamPlaying, player);
this->SetPlaybackState(Transport::PlaybackPlaying);
}
void Transport::OnPlaybackAlmostEnded(Player* player) {
this->SetNextCanStart(true);
this->RaiseStreamEvent(Transport::StreamAlmostDone, player);
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
/* if another component configured a next player while we were playing,
go ahead and get it started now. */
if (this->nextPlayer) {
this->StartWithPlayer(this->nextPlayer);
}
}
}
void Transport::OnPlaybackFinished(Player* player) {
this->SetNextCanStart(true);
this->RaiseStreamEvent(Transport::StreamFinished, player);
bool stopped = false;
@ -320,23 +337,8 @@ void Transport::OnPlaybackFinished(Player* player) {
DEFER(&Transport::RemoveActive, player);
}
void Transport::OnPlaybackStopped (Player* player) {
this->RaiseStreamEvent(Transport::StreamStopped, player);
bool stopped = false;
{
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
stopped = !this->active.size();
}
if (stopped) {
this->SetPlaybackState(Transport::PlaybackStopped);
}
DEFER(&Transport::RemoveActive, player);
}
void Transport::OnPlaybackError(Player* player) {
this->SetNextCanStart(true);
this->RaiseStreamEvent(Transport::StreamError, player);
this->SetPlaybackState(Transport::PlaybackStopped);
DEFER(&Transport::RemoveActive, player);

View File

@ -84,9 +84,10 @@ namespace musik { namespace core { namespace audio {
private:
void StartWithPlayer(Player* player);
void Stop(bool suppressEvent);
void Stop(bool stopOutput);
void RemoveActive(Player* player);
void DeletePlayers(std::list<Player*> players);
void SetNextCanStart(bool nextCanStart);
void RaiseStreamEvent(int type, Player* player);
void SetPlaybackState(int state);
@ -94,16 +95,15 @@ namespace musik { namespace core { namespace audio {
void OnPlaybackStarted(Player* player);
void OnPlaybackAlmostEnded(Player* player);
void OnPlaybackFinished(Player* player);
void OnPlaybackStopped(Player* player);
void OnPlaybackError(Player* player);
private:
double volume;
PlaybackState state;
bool nextCanStart;
boost::recursive_mutex stateMutex;
Player* nextPlayer;
std::shared_ptr<IOutput> output;
musik::core::audio::OutputPtr output;
std::list<Player*> active;
};

View File

@ -175,7 +175,7 @@ static inline std::string readKeyPress(int64 ch) {
}
static inline int64 now() {
return duration_cast<milliseconds>(
return duration_cast<milliseconds>(
system_clock::now().time_since_epoch()).count();
}