PlayerThread, ...: move to src/player/
This commit is contained in:
267
src/player/Control.cxx
Normal file
267
src/player/Control.cxx
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Control.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "DetachedSong.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
PlayerControl::PlayerControl(PlayerListener &_listener,
|
||||
MultipleOutputs &_outputs,
|
||||
unsigned _buffer_chunks,
|
||||
unsigned _buffered_before_play)
|
||||
:listener(_listener), outputs(_outputs),
|
||||
buffer_chunks(_buffer_chunks),
|
||||
buffered_before_play(_buffered_before_play),
|
||||
command(PlayerCommand::NONE),
|
||||
state(PlayerState::STOP),
|
||||
error_type(PlayerError::NONE),
|
||||
tagged_song(nullptr),
|
||||
next_song(nullptr),
|
||||
total_play_time(0),
|
||||
border_pause(false)
|
||||
{
|
||||
}
|
||||
|
||||
PlayerControl::~PlayerControl()
|
||||
{
|
||||
delete next_song;
|
||||
delete tagged_song;
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::Play(DetachedSong *song)
|
||||
{
|
||||
assert(song != nullptr);
|
||||
|
||||
Lock();
|
||||
|
||||
if (state != PlayerState::STOP)
|
||||
SynchronousCommand(PlayerCommand::STOP);
|
||||
|
||||
assert(next_song == nullptr);
|
||||
|
||||
EnqueueSongLocked(song);
|
||||
|
||||
assert(next_song == nullptr);
|
||||
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::Cancel()
|
||||
{
|
||||
LockSynchronousCommand(PlayerCommand::CANCEL);
|
||||
assert(next_song == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::Stop()
|
||||
{
|
||||
LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO);
|
||||
assert(next_song == nullptr);
|
||||
|
||||
idle_add(IDLE_PLAYER);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::UpdateAudio()
|
||||
{
|
||||
LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::Kill()
|
||||
{
|
||||
assert(thread.IsDefined());
|
||||
|
||||
LockSynchronousCommand(PlayerCommand::EXIT);
|
||||
thread.Join();
|
||||
|
||||
idle_add(IDLE_PLAYER);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::PauseLocked()
|
||||
{
|
||||
if (state != PlayerState::STOP) {
|
||||
SynchronousCommand(PlayerCommand::PAUSE);
|
||||
idle_add(IDLE_PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::Pause()
|
||||
{
|
||||
Lock();
|
||||
PauseLocked();
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::SetPause(bool pause_flag)
|
||||
{
|
||||
Lock();
|
||||
|
||||
switch (state) {
|
||||
case PlayerState::STOP:
|
||||
break;
|
||||
|
||||
case PlayerState::PLAY:
|
||||
if (pause_flag)
|
||||
PauseLocked();
|
||||
break;
|
||||
|
||||
case PlayerState::PAUSE:
|
||||
if (!pause_flag)
|
||||
PauseLocked();
|
||||
break;
|
||||
}
|
||||
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::SetBorderPause(bool _border_pause)
|
||||
{
|
||||
Lock();
|
||||
border_pause = _border_pause;
|
||||
Unlock();
|
||||
}
|
||||
|
||||
player_status
|
||||
PlayerControl::GetStatus()
|
||||
{
|
||||
player_status status;
|
||||
|
||||
Lock();
|
||||
SynchronousCommand(PlayerCommand::REFRESH);
|
||||
|
||||
status.state = state;
|
||||
|
||||
if (state != PlayerState::STOP) {
|
||||
status.bit_rate = bit_rate;
|
||||
status.audio_format = audio_format;
|
||||
status.total_time = total_time;
|
||||
status.elapsed_time = elapsed_time;
|
||||
}
|
||||
|
||||
Unlock();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::SetError(PlayerError type, Error &&_error)
|
||||
{
|
||||
assert(type != PlayerError::NONE);
|
||||
assert(_error.IsDefined());
|
||||
|
||||
error_type = type;
|
||||
error = std::move(_error);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::ClearError()
|
||||
{
|
||||
Lock();
|
||||
|
||||
if (error_type != PlayerError::NONE) {
|
||||
error_type = PlayerError::NONE;
|
||||
error.Clear();
|
||||
}
|
||||
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::LockSetTaggedSong(const DetachedSong &song)
|
||||
{
|
||||
Lock();
|
||||
delete tagged_song;
|
||||
tagged_song = new DetachedSong(song);
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::ClearTaggedSong()
|
||||
{
|
||||
delete tagged_song;
|
||||
tagged_song = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::EnqueueSong(DetachedSong *song)
|
||||
{
|
||||
assert(song != nullptr);
|
||||
|
||||
Lock();
|
||||
EnqueueSongLocked(song);
|
||||
Unlock();
|
||||
}
|
||||
|
||||
bool
|
||||
PlayerControl::Seek(DetachedSong *song, SongTime t)
|
||||
{
|
||||
assert(song != nullptr);
|
||||
|
||||
Lock();
|
||||
|
||||
delete next_song;
|
||||
next_song = song;
|
||||
seek_time = t;
|
||||
SynchronousCommand(PlayerCommand::SEEK);
|
||||
Unlock();
|
||||
|
||||
assert(next_song == nullptr);
|
||||
|
||||
idle_add(IDLE_PLAYER);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::SetCrossFade(float _cross_fade_seconds)
|
||||
{
|
||||
if (_cross_fade_seconds < 0)
|
||||
_cross_fade_seconds = 0;
|
||||
cross_fade.duration = _cross_fade_seconds;
|
||||
|
||||
idle_add(IDLE_OPTIONS);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::SetMixRampDb(float _mixramp_db)
|
||||
{
|
||||
cross_fade.mixramp_db = _mixramp_db;
|
||||
|
||||
idle_add(IDLE_OPTIONS);
|
||||
}
|
||||
|
||||
void
|
||||
PlayerControl::SetMixRampDelay(float _mixramp_delay_seconds)
|
||||
{
|
||||
cross_fade.mixramp_delay = _mixramp_delay_seconds;
|
||||
|
||||
idle_add(IDLE_OPTIONS);
|
||||
}
|
||||
463
src/player/Control.hxx
Normal file
463
src/player/Control.hxx
Normal file
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_PLAYER_CONTROL_HXX
|
||||
#define MPD_PLAYER_CONTROL_HXX
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
#include "thread/Thread.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "CrossFade.hxx"
|
||||
#include "Chrono.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class PlayerListener;
|
||||
class MultipleOutputs;
|
||||
class DetachedSong;
|
||||
|
||||
enum class PlayerState : uint8_t {
|
||||
STOP,
|
||||
PAUSE,
|
||||
PLAY
|
||||
};
|
||||
|
||||
enum class PlayerCommand : uint8_t {
|
||||
NONE,
|
||||
EXIT,
|
||||
STOP,
|
||||
PAUSE,
|
||||
SEEK,
|
||||
CLOSE_AUDIO,
|
||||
|
||||
/**
|
||||
* At least one AudioOutput.enabled flag has been modified;
|
||||
* commit those changes to the output threads.
|
||||
*/
|
||||
UPDATE_AUDIO,
|
||||
|
||||
/** PlayerControl.next_song has been updated */
|
||||
QUEUE,
|
||||
|
||||
/**
|
||||
* cancel pre-decoding PlayerControl.next_song; if the player
|
||||
* has already started playing this song, it will completely
|
||||
* stop
|
||||
*/
|
||||
CANCEL,
|
||||
|
||||
/**
|
||||
* Refresh status information in the #PlayerControl struct,
|
||||
* e.g. elapsed_time.
|
||||
*/
|
||||
REFRESH,
|
||||
};
|
||||
|
||||
enum class PlayerError : uint8_t {
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* The decoder has failed to decode the song.
|
||||
*/
|
||||
DECODER,
|
||||
|
||||
/**
|
||||
* The audio output has failed.
|
||||
*/
|
||||
OUTPUT,
|
||||
};
|
||||
|
||||
struct player_status {
|
||||
PlayerState state;
|
||||
uint16_t bit_rate;
|
||||
AudioFormat audio_format;
|
||||
SignedSongTime total_time;
|
||||
SongTime elapsed_time;
|
||||
};
|
||||
|
||||
struct PlayerControl {
|
||||
PlayerListener &listener;
|
||||
|
||||
MultipleOutputs &outputs;
|
||||
|
||||
const unsigned buffer_chunks;
|
||||
|
||||
const unsigned buffered_before_play;
|
||||
|
||||
/**
|
||||
* The handle of the player thread.
|
||||
*/
|
||||
Thread thread;
|
||||
|
||||
/**
|
||||
* This lock protects #command, #state, #error, #tagged_song.
|
||||
*/
|
||||
mutable Mutex mutex;
|
||||
|
||||
/**
|
||||
* Trigger this object after you have modified #command.
|
||||
*/
|
||||
Cond cond;
|
||||
|
||||
/**
|
||||
* This object gets signalled when the player thread has
|
||||
* finished the #command. It wakes up the client that waits
|
||||
* (i.e. the main thread).
|
||||
*/
|
||||
Cond client_cond;
|
||||
|
||||
PlayerCommand command;
|
||||
PlayerState state;
|
||||
|
||||
PlayerError error_type;
|
||||
|
||||
/**
|
||||
* The error that occurred in the player thread. This
|
||||
* attribute is only valid if #error is not
|
||||
* #PlayerError::NONE. The object must be freed when this
|
||||
* object transitions back to #PlayerError::NONE.
|
||||
*/
|
||||
Error error;
|
||||
|
||||
/**
|
||||
* A copy of the current #DetachedSong after its tags have
|
||||
* been updated by the decoder (for example, a radio stream
|
||||
* that has sent a new tag after switching to the next song).
|
||||
* This shall be used by PlayerListener::OnPlayerTagModified()
|
||||
* to update the current #DetachedSong in the queue.
|
||||
*
|
||||
* Protected by #mutex. Set by the PlayerThread and consumed
|
||||
* by the main thread.
|
||||
*/
|
||||
DetachedSong *tagged_song;
|
||||
|
||||
uint16_t bit_rate;
|
||||
AudioFormat audio_format;
|
||||
SignedSongTime total_time;
|
||||
SongTime elapsed_time;
|
||||
|
||||
/**
|
||||
* The next queued song.
|
||||
*
|
||||
* This is a duplicate, and must be freed when this attribute
|
||||
* is cleared.
|
||||
*/
|
||||
DetachedSong *next_song;
|
||||
|
||||
SongTime seek_time;
|
||||
|
||||
CrossFadeSettings cross_fade;
|
||||
|
||||
double total_play_time;
|
||||
|
||||
/**
|
||||
* If this flag is set, then the player will be auto-paused at
|
||||
* the end of the song, before the next song starts to play.
|
||||
*
|
||||
* This is a copy of the queue's "single" flag most of the
|
||||
* time.
|
||||
*/
|
||||
bool border_pause;
|
||||
|
||||
PlayerControl(PlayerListener &_listener,
|
||||
MultipleOutputs &_outputs,
|
||||
unsigned buffer_chunks,
|
||||
unsigned buffered_before_play);
|
||||
~PlayerControl();
|
||||
|
||||
/**
|
||||
* Locks the object.
|
||||
*/
|
||||
void Lock() const {
|
||||
mutex.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the object.
|
||||
*/
|
||||
void Unlock() const {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the object. The object should be locked prior to
|
||||
* calling this function.
|
||||
*/
|
||||
void Signal() {
|
||||
cond.signal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the object. The object is temporarily locked by
|
||||
* this function.
|
||||
*/
|
||||
void LockSignal() {
|
||||
Lock();
|
||||
Signal();
|
||||
Unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a signal on the object. This function is only
|
||||
* valid in the player thread. The object must be locked
|
||||
* prior to calling this function.
|
||||
*/
|
||||
void Wait() {
|
||||
assert(thread.IsInside());
|
||||
|
||||
cond.wait(mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wake up the client waiting for command completion.
|
||||
*
|
||||
* Caller must lock the object.
|
||||
*/
|
||||
void ClientSignal() {
|
||||
assert(thread.IsInside());
|
||||
|
||||
client_cond.signal();
|
||||
}
|
||||
|
||||
/**
|
||||
* The client calls this method to wait for command
|
||||
* completion.
|
||||
*
|
||||
* Caller must lock the object.
|
||||
*/
|
||||
void ClientWait() {
|
||||
assert(!thread.IsInside());
|
||||
|
||||
client_cond.wait(mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* A command has been finished. This method clears the
|
||||
* command and signals the client.
|
||||
*
|
||||
* To be called from the player thread. Caller must lock the
|
||||
* object.
|
||||
*/
|
||||
void CommandFinished() {
|
||||
assert(command != PlayerCommand::NONE);
|
||||
|
||||
command = PlayerCommand::NONE;
|
||||
ClientSignal();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Wait for the command to be finished by the player thread.
|
||||
*
|
||||
* To be called from the main thread. Caller must lock the
|
||||
* object.
|
||||
*/
|
||||
void WaitCommandLocked() {
|
||||
while (command != PlayerCommand::NONE)
|
||||
ClientWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the player thread and synchronously wait
|
||||
* for it to finish.
|
||||
*
|
||||
* To be called from the main thread. Caller must lock the
|
||||
* object.
|
||||
*/
|
||||
void SynchronousCommand(PlayerCommand cmd) {
|
||||
assert(command == PlayerCommand::NONE);
|
||||
|
||||
command = cmd;
|
||||
Signal();
|
||||
WaitCommandLocked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the player thread and synchronously wait
|
||||
* for it to finish.
|
||||
*
|
||||
* To be called from the main thread. This method locks the
|
||||
* object.
|
||||
*/
|
||||
void LockSynchronousCommand(PlayerCommand cmd) {
|
||||
Lock();
|
||||
SynchronousCommand(cmd);
|
||||
Unlock();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param song the song to be queued; the given instance will
|
||||
* be owned and freed by the player
|
||||
*/
|
||||
void Play(DetachedSong *song);
|
||||
|
||||
/**
|
||||
* see PlayerCommand::CANCEL
|
||||
*/
|
||||
void Cancel();
|
||||
|
||||
void SetPause(bool pause_flag);
|
||||
|
||||
private:
|
||||
void PauseLocked();
|
||||
|
||||
public:
|
||||
void Pause();
|
||||
|
||||
/**
|
||||
* Set the player's #border_pause flag.
|
||||
*/
|
||||
void SetBorderPause(bool border_pause);
|
||||
|
||||
void Kill();
|
||||
|
||||
gcc_pure
|
||||
player_status GetStatus();
|
||||
|
||||
PlayerState GetState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error. Discards any previous error condition.
|
||||
*
|
||||
* Caller must lock the object.
|
||||
*
|
||||
* @param type the error type; must not be #PlayerError::NONE
|
||||
* @param error detailed error information; must be defined.
|
||||
*/
|
||||
void SetError(PlayerError type, Error &&error);
|
||||
|
||||
/**
|
||||
* Checks whether an error has occurred, and if so, returns a
|
||||
* copy of the #Error object.
|
||||
*
|
||||
* Caller must lock the object.
|
||||
*/
|
||||
gcc_pure
|
||||
Error GetError() const {
|
||||
Error result;
|
||||
if (error_type != PlayerError::NONE)
|
||||
result.Set(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like GetError(), but locks and unlocks the object.
|
||||
*/
|
||||
gcc_pure
|
||||
Error LockGetError() const {
|
||||
Lock();
|
||||
Error result = GetError();
|
||||
Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearError();
|
||||
|
||||
PlayerError GetErrorType() const {
|
||||
return error_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the #tagged_song attribute to a newly allocated copy of
|
||||
* the given #DetachedSong. Locks and unlocks the object.
|
||||
*/
|
||||
void LockSetTaggedSong(const DetachedSong &song);
|
||||
|
||||
void ClearTaggedSong();
|
||||
|
||||
/**
|
||||
* Read and clear the #tagged_song attribute.
|
||||
*
|
||||
* Caller must lock the object.
|
||||
*/
|
||||
DetachedSong *ReadTaggedSong() {
|
||||
DetachedSong *result = tagged_song;
|
||||
tagged_song = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like ReadTaggedSong(), but locks and unlocks the object.
|
||||
*/
|
||||
DetachedSong *LockReadTaggedSong() {
|
||||
Lock();
|
||||
DetachedSong *result = ReadTaggedSong();
|
||||
Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Stop();
|
||||
|
||||
void UpdateAudio();
|
||||
|
||||
private:
|
||||
void EnqueueSongLocked(DetachedSong *song) {
|
||||
assert(song != nullptr);
|
||||
assert(next_song == nullptr);
|
||||
|
||||
next_song = song;
|
||||
SynchronousCommand(PlayerCommand::QUEUE);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param song the song to be queued; the given instance will be owned
|
||||
* and freed by the player
|
||||
*/
|
||||
void EnqueueSong(DetachedSong *song);
|
||||
|
||||
/**
|
||||
* Makes the player thread seek the specified song to a position.
|
||||
*
|
||||
* @param song the song to be queued; the given instance will be owned
|
||||
* and freed by the player
|
||||
* @return true on success, false on failure (e.g. if MPD isn't
|
||||
* playing currently)
|
||||
*/
|
||||
bool Seek(DetachedSong *song, SongTime t);
|
||||
|
||||
void SetCrossFade(float cross_fade_seconds);
|
||||
|
||||
float GetCrossFade() const {
|
||||
return cross_fade.duration;
|
||||
}
|
||||
|
||||
void SetMixRampDb(float mixramp_db);
|
||||
|
||||
float GetMixRampDb() const {
|
||||
return cross_fade.mixramp_db;
|
||||
}
|
||||
|
||||
void SetMixRampDelay(float mixramp_delay_seconds);
|
||||
|
||||
float GetMixRampDelay() const {
|
||||
return cross_fade.mixramp_delay;
|
||||
}
|
||||
|
||||
double GetTotalPlayTime() const {
|
||||
return total_play_time;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
140
src/player/CrossFade.cxx
Normal file
140
src/player/CrossFade.cxx
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "CrossFade.hxx"
|
||||
#include "Chrono.hxx"
|
||||
#include "MusicChunk.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static constexpr Domain cross_fade_domain("cross_fade");
|
||||
|
||||
gcc_pure
|
||||
static float
|
||||
mixramp_interpolate(const char *ramp_list, float required_db)
|
||||
{
|
||||
float last_db = 0, last_secs = 0;
|
||||
bool have_last = false;
|
||||
|
||||
/* ramp_list is a string of pairs of dBs and seconds that describe the
|
||||
* volume profile. Delimiters are semi-colons between pairs and spaces
|
||||
* between the dB and seconds of a pair.
|
||||
* The dB values must be monotonically increasing for this to work. */
|
||||
|
||||
while (1) {
|
||||
/* Parse the dB value. */
|
||||
char *endptr;
|
||||
const float db = ParseFloat(ramp_list, &endptr);
|
||||
if (endptr == ramp_list || *endptr != ' ')
|
||||
break;
|
||||
|
||||
ramp_list = endptr + 1;
|
||||
|
||||
/* Parse the time. */
|
||||
float secs = ParseFloat(ramp_list, &endptr);
|
||||
if (endptr == ramp_list || (*endptr != ';' && *endptr != 0))
|
||||
break;
|
||||
|
||||
ramp_list = endptr;
|
||||
if (*ramp_list == ';')
|
||||
++ramp_list;
|
||||
|
||||
/* Check for exact match. */
|
||||
if (db == required_db) {
|
||||
return secs;
|
||||
}
|
||||
|
||||
/* Save if too quiet. */
|
||||
if (db < required_db) {
|
||||
last_db = db;
|
||||
last_secs = secs;
|
||||
have_last = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If required db < any stored value, use the least. */
|
||||
if (!have_last)
|
||||
return secs;
|
||||
|
||||
/* Finally, interpolate linearly. */
|
||||
secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
|
||||
return secs;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned
|
||||
CrossFadeSettings::Calculate(SignedSongTime total_time,
|
||||
float replay_gain_db, float replay_gain_prev_db,
|
||||
const char *mixramp_start, const char *mixramp_prev_end,
|
||||
const AudioFormat af,
|
||||
const AudioFormat old_format,
|
||||
unsigned max_chunks) const
|
||||
{
|
||||
unsigned int chunks = 0;
|
||||
float chunks_f;
|
||||
|
||||
if (total_time.IsNegative() ||
|
||||
duration < 0 || duration >= total_time.ToDoubleS() ||
|
||||
/* we can't crossfade when the audio formats are different */
|
||||
af != old_format)
|
||||
return 0;
|
||||
|
||||
assert(duration >= 0);
|
||||
assert(af.IsValid());
|
||||
|
||||
chunks_f = (float)af.GetTimeToSize() / (float)CHUNK_SIZE;
|
||||
|
||||
if (mixramp_delay <= 0 || !mixramp_start || !mixramp_prev_end) {
|
||||
chunks = (chunks_f * duration + 0.5);
|
||||
} else {
|
||||
/* Calculate mixramp overlap. */
|
||||
const float mixramp_overlap_current =
|
||||
mixramp_interpolate(mixramp_start,
|
||||
mixramp_db - replay_gain_db);
|
||||
const float mixramp_overlap_prev =
|
||||
mixramp_interpolate(mixramp_prev_end,
|
||||
mixramp_db - replay_gain_prev_db);
|
||||
const float mixramp_overlap =
|
||||
mixramp_overlap_current + mixramp_overlap_prev;
|
||||
|
||||
if (mixramp_overlap_current >= 0 &&
|
||||
mixramp_overlap_prev >= 0 &&
|
||||
mixramp_delay <= mixramp_overlap) {
|
||||
chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
|
||||
FormatDebug(cross_fade_domain,
|
||||
"will overlap %d chunks, %fs", chunks,
|
||||
mixramp_overlap - mixramp_delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (chunks > max_chunks) {
|
||||
chunks = max_chunks;
|
||||
LogWarning(cross_fade_domain,
|
||||
"audio_buffer_size too small for computed MixRamp overlap");
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
72
src/player/CrossFade.hxx
Normal file
72
src/player/CrossFade.hxx
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_CROSSFADE_HXX
|
||||
#define MPD_CROSSFADE_HXX
|
||||
|
||||
#include "Compiler.h"
|
||||
|
||||
struct AudioFormat;
|
||||
class SignedSongTime;
|
||||
|
||||
struct CrossFadeSettings {
|
||||
/**
|
||||
* The configured cross fade duration [s].
|
||||
*/
|
||||
float duration;
|
||||
|
||||
float mixramp_db;
|
||||
|
||||
/**
|
||||
* The configured MixRapm delay [s]. A non-positive value
|
||||
* disables MixRamp.
|
||||
*/
|
||||
float mixramp_delay;
|
||||
|
||||
CrossFadeSettings()
|
||||
:duration(0),
|
||||
mixramp_db(0),
|
||||
mixramp_delay(-1)
|
||||
{}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate how many music pipe chunks should be used for crossfading.
|
||||
*
|
||||
* @param total_time total_time the duration of the new song
|
||||
* @param replay_gain_db the ReplayGain adjustment used for this song
|
||||
* @param replay_gain_prev_db the ReplayGain adjustment used on the last song
|
||||
* @param mixramp_start the next songs mixramp_start tag
|
||||
* @param mixramp_prev_end the last songs mixramp_end setting
|
||||
* @param af the audio format of the new song
|
||||
* @param old_format the audio format of the current song
|
||||
* @param max_chunks the maximum number of chunks
|
||||
* @return the number of chunks for crossfading, or 0 if cross fading
|
||||
* should be disabled for this song change
|
||||
*/
|
||||
gcc_pure
|
||||
unsigned Calculate(SignedSongTime total_time,
|
||||
float replay_gain_db, float replay_gain_prev_db,
|
||||
const char *mixramp_start,
|
||||
const char *mixramp_prev_end,
|
||||
AudioFormat af, AudioFormat old_format,
|
||||
unsigned max_chunks) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
36
src/player/Listener.hxx
Normal file
36
src/player/Listener.hxx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_PLAYER_LISTENER_HXX
|
||||
#define MPD_PLAYER_LISTENER_HXX
|
||||
|
||||
class PlayerListener {
|
||||
public:
|
||||
/**
|
||||
* Must call playlist_sync().
|
||||
*/
|
||||
virtual void OnPlayerSync() = 0;
|
||||
|
||||
/**
|
||||
* The current song's tag has changed.
|
||||
*/
|
||||
virtual void OnPlayerTagModified() = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
1208
src/player/Thread.cxx
Normal file
1208
src/player/Thread.cxx
Normal file
File diff suppressed because it is too large
Load Diff
45
src/player/Thread.hxx
Normal file
45
src/player/Thread.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* \file
|
||||
*
|
||||
* The player thread controls the playback. It acts as a bridge
|
||||
* between the decoder thread and the output thread(s): it receives
|
||||
* #MusicChunk objects from the decoder, optionally mixes them
|
||||
* (cross-fading), applies software volume, and sends them to the
|
||||
* audio outputs via audio_output_all_play().
|
||||
*
|
||||
* It is controlled by the main thread (the playlist code), see
|
||||
* Control.hxx. The playlist enqueues new songs into the player
|
||||
* thread and sends it commands.
|
||||
*
|
||||
* The player thread itself does not do any I/O. It synchronizes with
|
||||
* other threads via #GMutex and #GCond objects, and passes
|
||||
* #MusicChunk instances around in #MusicPipe objects.
|
||||
*/
|
||||
|
||||
#ifndef MPD_PLAYER_THREAD_HXX
|
||||
#define MPD_PLAYER_THREAD_HXX
|
||||
|
||||
struct PlayerControl;
|
||||
|
||||
void
|
||||
StartPlayerThread(PlayerControl &pc);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user