mirror of
https://github.com/clangen/musikcube.git
synced 2025-02-23 00:39:57 +00:00
Moved Player/Transport interaction to using a simple callback/listener
interface instead of events. Events were causing complications with multi-threading, and introducing unnecessary runtime complexity.
This commit is contained in:
parent
c58607f014
commit
3657a995a1
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
#include "AlsaOut.h"
|
#include "AlsaOut.h"
|
||||||
|
|
||||||
#define BUFFER_COUNT 8
|
#define BUFFER_COUNT 32
|
||||||
#define PCM_ACCESS_TYPE SND_PCM_ACCESS_RW_INTERLEAVED
|
#define PCM_ACCESS_TYPE SND_PCM_ACCESS_RW_INTERLEAVED
|
||||||
#define PCM_FORMAT SND_PCM_FORMAT_FLOAT_LE
|
#define PCM_FORMAT SND_PCM_FORMAT_FLOAT_LE
|
||||||
|
|
||||||
@ -56,9 +56,14 @@
|
|||||||
static inline bool playable(snd_pcm_t* pcm) {
|
static inline bool playable(snd_pcm_t* pcm) {
|
||||||
snd_pcm_state_t state = snd_pcm_state(pcm);
|
snd_pcm_state_t state = snd_pcm_state(pcm);
|
||||||
|
|
||||||
return
|
if (state == SND_PCM_STATE_RUNNING ||
|
||||||
state == SND_PCM_STATE_RUNNING ||
|
state == SND_PCM_STATE_PREPARED)
|
||||||
state == SND_PCM_STATE_PREPARED;
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "AlsaOut: invalid device state: " << (int) state << "\n";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace musik::core::sdk;
|
using namespace musik::core::sdk;
|
||||||
@ -238,7 +243,7 @@ void AlsaOut::WriteLoop() {
|
|||||||
float volume = (float) this->volume;
|
float volume = (float) this->volume;
|
||||||
|
|
||||||
/* software volume; alsa doesn't support this internally. this is about
|
/* software volume; alsa doesn't support this internally. this is about
|
||||||
as terrible as an algorithm can be -- it's just a linear ramp. */\
|
as terrible as an algorithm can be -- it's just a linear ramp. */
|
||||||
if (volume != 1.0f) {
|
if (volume != 1.0f) {
|
||||||
float *buffer = next->buffer->BufferPointer();
|
float *buffer = next->buffer->BufferPointer();
|
||||||
for (size_t i = 0; i < samples; i++) {
|
for (size_t i = 0; i < samples; i++) {
|
||||||
@ -272,12 +277,14 @@ bool AlsaOut::Play(IBuffer *buffer, IBufferProvider* provider) {
|
|||||||
{
|
{
|
||||||
LOCK("play");
|
LOCK("play");
|
||||||
|
|
||||||
if (!playable(this->pcmHandle) ||
|
if (this->CountBuffersWithProvider(provider) >= BUFFER_COUNT) {
|
||||||
this->CountBuffersWithProvider(provider) >= BUFFER_COUNT)
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!playable(this->pcmHandle)) {
|
||||||
|
std::cerr << "AlsaOut: sanity check -- stream not playable. adding buffer to queue anyway\n";
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<BufferContext> context(new BufferContext());
|
std::shared_ptr<BufferContext> context(new BufferContext());
|
||||||
context->buffer = buffer;
|
context->buffer = buffer;
|
||||||
context->provider = provider;
|
context->provider = provider;
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#define BUFFER_COUNT 16
|
#define BUFFER_COUNT 32
|
||||||
|
|
||||||
using namespace musik::core::sdk;
|
using namespace musik::core::sdk;
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
#include "WaveOut.h"
|
#include "WaveOut.h"
|
||||||
|
|
||||||
#define MAX_VOLUME 0xFFFF
|
#define MAX_VOLUME 0xFFFF
|
||||||
#define MAX_BUFFERS_PER_OUTPUT 16
|
#define MAX_BUFFERS_PER_OUTPUT 32
|
||||||
|
|
||||||
static void notifyBufferProcessed(WaveOutBuffer *buffer) {
|
static void notifyBufferProcessed(WaveOutBuffer *buffer) {
|
||||||
/* let the provider know the output device is done with the buffer; the
|
/* let the provider know the output device is done with the buffer; the
|
||||||
|
@ -45,18 +45,11 @@ using namespace musik::core::sdk;
|
|||||||
|
|
||||||
static std::string TAG = "Transport";
|
static std::string TAG = "Transport";
|
||||||
|
|
||||||
#define DISCONNECT_AND_DESTROY_PLAYER(instance, player) \
|
|
||||||
if (player) { \
|
|
||||||
player->PlaybackAlmostEnded.disconnect(instance); \
|
|
||||||
player->PlaybackError.disconnect(instance); \
|
|
||||||
player->PlaybackFinished.disconnect(instance); \
|
|
||||||
player->PlaybackStarted.disconnect(instance); \
|
|
||||||
player->Destroy(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define RESET_NEXT_PLAYER(instance) \
|
#define RESET_NEXT_PLAYER(instance) \
|
||||||
DISCONNECT_AND_DESTROY_PLAYER(instance, this->nextPlayer); \
|
if (instance->nextPlayer) { \
|
||||||
instance->nextPlayer = nullptr;
|
instance->nextPlayer->Destroy(); \
|
||||||
|
instance->nextPlayer = nullptr; \
|
||||||
|
}
|
||||||
|
|
||||||
GaplessTransport::GaplessTransport()
|
GaplessTransport::GaplessTransport()
|
||||||
: volume(1.0)
|
: volume(1.0)
|
||||||
@ -78,7 +71,7 @@ GaplessTransport::~GaplessTransport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = players.begin(); it != players.end(); it++) {
|
for (auto it = players.begin(); it != players.end(); it++) {
|
||||||
DISCONNECT_AND_DESTROY_PLAYER(this, (*it));
|
(*it)->Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +85,7 @@ void GaplessTransport::PrepareNextTrack(const std::string& trackUrl) {
|
|||||||
{
|
{
|
||||||
LockT lock(this->stateMutex);
|
LockT lock(this->stateMutex);
|
||||||
RESET_NEXT_PLAYER(this);
|
RESET_NEXT_PLAYER(this);
|
||||||
this->nextPlayer = Player::Create(trackUrl, this->output);
|
this->nextPlayer = Player::Create(trackUrl, this->output, this);
|
||||||
startNext = this->nextCanStart;
|
startNext = this->nextCanStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +97,7 @@ void GaplessTransport::PrepareNextTrack(const std::string& trackUrl) {
|
|||||||
void GaplessTransport::Start(const std::string& url) {
|
void GaplessTransport::Start(const std::string& url) {
|
||||||
musik::debug::info(TAG, "we were asked to start the track at " + url);
|
musik::debug::info(TAG, "we were asked to start the track at " + url);
|
||||||
|
|
||||||
Player* newPlayer = Player::Create(url, this->output);
|
Player* newPlayer = Player::Create(url, this->output, this);
|
||||||
musik::debug::info(TAG, "Player created successfully");
|
musik::debug::info(TAG, "Player created successfully");
|
||||||
|
|
||||||
this->StartWithPlayer(newPlayer);
|
this->StartWithPlayer(newPlayer);
|
||||||
@ -112,11 +105,6 @@ void GaplessTransport::Start(const std::string& url) {
|
|||||||
|
|
||||||
void GaplessTransport::StartWithPlayer(Player* newPlayer) {
|
void GaplessTransport::StartWithPlayer(Player* newPlayer) {
|
||||||
if (newPlayer) {
|
if (newPlayer) {
|
||||||
newPlayer->PlaybackStarted.connect(this, &GaplessTransport::OnPlaybackStarted);
|
|
||||||
newPlayer->PlaybackAlmostEnded.connect(this, &GaplessTransport::OnPlaybackAlmostEnded);
|
|
||||||
newPlayer->PlaybackFinished.connect(this, &GaplessTransport::OnPlaybackFinished);
|
|
||||||
newPlayer->PlaybackError.connect(this, &GaplessTransport::OnPlaybackError);
|
|
||||||
|
|
||||||
bool playingNext = false;
|
bool playingNext = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -124,7 +112,7 @@ void GaplessTransport::StartWithPlayer(Player* newPlayer) {
|
|||||||
|
|
||||||
playingNext = (newPlayer == nextPlayer);
|
playingNext = (newPlayer == nextPlayer);
|
||||||
if (nextPlayer != nullptr && newPlayer != nextPlayer) {
|
if (nextPlayer != nullptr && newPlayer != nextPlayer) {
|
||||||
DISCONNECT_AND_DESTROY_PLAYER(this, this->nextPlayer);
|
this->nextPlayer->Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->nextPlayer = nullptr;
|
this->nextPlayer = nullptr;
|
||||||
@ -172,7 +160,7 @@ void GaplessTransport::StopInternal(
|
|||||||
auto it = this->active.begin();
|
auto it = this->active.begin();
|
||||||
while (it != this->active.end()) {
|
while (it != this->active.end()) {
|
||||||
if (*it != exclude) {
|
if (*it != exclude) {
|
||||||
DISCONNECT_AND_DESTROY_PLAYER(this, (*it));
|
(*it)->Destroy();
|
||||||
it = this->active.erase(it);
|
it = this->active.erase(it);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -311,7 +299,7 @@ void GaplessTransport::RemoveFromActive(Player* player) {
|
|||||||
|
|
||||||
/* outside of the critical section, otherwise potential deadlock */
|
/* outside of the critical section, otherwise potential deadlock */
|
||||||
if (found) {
|
if (found) {
|
||||||
DISCONNECT_AND_DESTROY_PLAYER(this, player);
|
player->Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
namespace musik { namespace core { namespace audio {
|
namespace musik { namespace core { namespace audio {
|
||||||
|
|
||||||
class GaplessTransport : public ITransport, public sigslot::has_slots<> {
|
class GaplessTransport : public ITransport, private Player::PlayerEventListener, public sigslot::has_slots<> {
|
||||||
public:
|
public:
|
||||||
GaplessTransport();
|
GaplessTransport();
|
||||||
virtual ~GaplessTransport();
|
virtual ~GaplessTransport();
|
||||||
@ -88,10 +88,10 @@ namespace musik { namespace core { namespace audio {
|
|||||||
void RaiseStreamEvent(int type, Player* player);
|
void RaiseStreamEvent(int type, Player* player);
|
||||||
void SetPlaybackState(int state);
|
void SetPlaybackState(int state);
|
||||||
|
|
||||||
void OnPlaybackStarted(Player* player);
|
virtual void OnPlaybackStarted(Player* player);
|
||||||
void OnPlaybackAlmostEnded(Player* player);
|
virtual void OnPlaybackAlmostEnded(Player* player);
|
||||||
void OnPlaybackFinished(Player* player);
|
virtual void OnPlaybackFinished(Player* player);
|
||||||
void OnPlaybackError(Player* player);
|
virtual void OnPlaybackError(Player* player);
|
||||||
|
|
||||||
musik::core::sdk::PlaybackState state;
|
musik::core::sdk::PlaybackState state;
|
||||||
std::recursive_mutex stateMutex;
|
std::recursive_mutex stateMutex;
|
||||||
|
@ -102,8 +102,8 @@ namespace musik {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Player* Player::Create(const std::string &url, OutputPtr output) {
|
Player* Player::Create(const std::string &url, OutputPtr output, PlayerEventListener *listener) {
|
||||||
return new Player(url, output);
|
return new Player(url, output, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputPtr Player::CreateDefaultOutput() {
|
OutputPtr Player::CreateDefaultOutput() {
|
||||||
@ -121,14 +121,15 @@ OutputPtr Player::CreateDefaultOutput() {
|
|||||||
return OutputPtr();
|
return OutputPtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player::Player(const std::string &url, OutputPtr output)
|
Player::Player(const std::string &url, OutputPtr output, PlayerEventListener *listener)
|
||||||
: state(Player::Precache)
|
: state(Player::Precache)
|
||||||
, url(url)
|
, url(url)
|
||||||
, currentPosition(0)
|
, currentPosition(0)
|
||||||
, output(output)
|
, output(output)
|
||||||
, notifiedStarted(false)
|
, notifiedStarted(false)
|
||||||
, setPosition(-1)
|
, setPosition(-1)
|
||||||
, fftContext(nullptr) {
|
, fftContext(nullptr)
|
||||||
|
, listener(listener) {
|
||||||
musik::debug::info(TAG, "new instance created");
|
musik::debug::info(TAG, "new instance created");
|
||||||
|
|
||||||
this->spectrum = new float[FFT_N / 2];
|
this->spectrum = new float[FFT_N / 2];
|
||||||
@ -288,10 +289,13 @@ void musik::core::audio::playerThreadLoop(Player* player) {
|
|||||||
buffer.reset(); /* important! we're done with this one locally. */
|
buffer.reset(); /* important! we're done with this one locally. */
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* the output device queue is full. we should block and wait until
|
/* the output device queue is probably full. we should block and wait until
|
||||||
the output lets us know that it needs more data */
|
the output lets us know that it needs more data. if we are starved for
|
||||||
|
more than a second, try to push the buffer into the output again. this
|
||||||
|
may happen if the sound driver has some sort of transient problem and
|
||||||
|
is temporarily unable to process the bufer (ALSA, i'm looking at you) */
|
||||||
std::unique_lock<std::mutex> lock(player->queueMutex);
|
std::unique_lock<std::mutex> lock(player->queueMutex);
|
||||||
player->writeToOutputCondition.wait(lock);
|
player->writeToOutputCondition.wait_for(lock, std::chrono::milliseconds(1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* if we're unable to obtain a buffer, it means we're out of data and the
|
/* if we're unable to obtain a buffer, it means we're out of data and the
|
||||||
@ -303,18 +307,18 @@ void musik::core::audio::playerThreadLoop(Player* player) {
|
|||||||
|
|
||||||
/* if the Quit flag isn't set, that means the stream has ended "naturally", i.e.
|
/* if the Quit flag isn't set, that means the stream has ended "naturally", i.e.
|
||||||
it wasn't stopped by the user. raise the "almost ended" flag. */
|
it wasn't stopped by the user. raise the "almost ended" flag. */
|
||||||
if (!player->Exited()) {
|
if (!player->Exited() && player->listener) {
|
||||||
player->PlaybackAlmostEnded(player);
|
player->listener->OnPlaybackAlmostEnded(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if the stream failed to open... */
|
/* if the stream failed to open... */
|
||||||
else {
|
else {
|
||||||
player->PlaybackError(player);
|
if (!player->Exited() && player->listener) {
|
||||||
|
player->listener->OnPlaybackError(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
player->state = Player::Quit;
|
|
||||||
|
|
||||||
/* unlock any remaining buffers... see comment above */
|
/* unlock any remaining buffers... see comment above */
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
player->OnBufferProcessed(buffer.get());
|
player->OnBufferProcessed(buffer.get());
|
||||||
@ -330,7 +334,11 @@ void musik::core::audio::playerThreadLoop(Player* player) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
player->PlaybackFinished(player);
|
if (!player->Exited() && player->listener) {
|
||||||
|
player->listener->OnPlaybackFinished(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
player->state = Player::Quit;
|
||||||
|
|
||||||
delete player;
|
delete player;
|
||||||
}
|
}
|
||||||
@ -484,6 +492,8 @@ void Player::OnBufferProcessed(IBuffer *buffer) {
|
|||||||
and send them to the output before they are actually processed by the
|
and send them to the output before they are actually processed by the
|
||||||
output device */
|
output device */
|
||||||
if (started) {
|
if (started) {
|
||||||
this->PlaybackStarted(this);
|
if (!this->Exited() && this->listener) {
|
||||||
|
this->listener->OnPlaybackStarted(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,11 +54,20 @@ namespace musik { namespace core { namespace audio {
|
|||||||
|
|
||||||
class Player : public musik::core::sdk::IBufferProvider {
|
class Player : public musik::core::sdk::IBufferProvider {
|
||||||
public:
|
public:
|
||||||
|
class PlayerEventListener {
|
||||||
|
public:
|
||||||
|
virtual void OnPlaybackStarted(Player *player) = 0;
|
||||||
|
virtual void OnPlaybackAlmostEnded(Player *player) = 0;
|
||||||
|
virtual void OnPlaybackFinished(Player *player) = 0;
|
||||||
|
virtual void OnPlaybackError(Player *player) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
static OutputPtr CreateDefaultOutput();
|
static OutputPtr CreateDefaultOutput();
|
||||||
|
|
||||||
static Player* Create(
|
static Player* Create(
|
||||||
const std::string &url,
|
const std::string &url,
|
||||||
OutputPtr output);
|
OutputPtr output,
|
||||||
|
PlayerEventListener *listener);
|
||||||
|
|
||||||
virtual void OnBufferProcessed(musik::core::sdk::IBuffer *buffer);
|
virtual void OnBufferProcessed(musik::core::sdk::IBuffer *buffer);
|
||||||
|
|
||||||
@ -72,12 +81,6 @@ namespace musik { namespace core { namespace audio {
|
|||||||
|
|
||||||
bool Exited();
|
bool Exited();
|
||||||
|
|
||||||
typedef sigslot::signal1<Player*> PlayerEvent;
|
|
||||||
PlayerEvent PlaybackStarted;
|
|
||||||
PlayerEvent PlaybackAlmostEnded;
|
|
||||||
PlayerEvent PlaybackFinished;
|
|
||||||
PlayerEvent PlaybackError;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool PreBuffer();
|
bool PreBuffer();
|
||||||
int State();
|
int State();
|
||||||
@ -88,7 +91,8 @@ namespace musik { namespace core { namespace audio {
|
|||||||
|
|
||||||
Player(
|
Player(
|
||||||
const std::string &url,
|
const std::string &url,
|
||||||
OutputPtr output);
|
OutputPtr output,
|
||||||
|
PlayerEventListener *listener);
|
||||||
|
|
||||||
virtual ~Player();
|
virtual ~Player();
|
||||||
|
|
||||||
@ -106,6 +110,7 @@ namespace musik { namespace core { namespace audio {
|
|||||||
StreamPtr stream;
|
StreamPtr stream;
|
||||||
ThreadPtr thread;
|
ThreadPtr thread;
|
||||||
BufferList lockedBuffers;
|
BufferList lockedBuffers;
|
||||||
|
PlayerEventListener* listener;
|
||||||
|
|
||||||
std::string url;
|
std::string url;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user