O Audacious é um reprodutor de áudio de código aberto. Ele descende do XMMS. Tenha total liberdade para arrastar pastas e arquivos de músicas, procure por álbuns e artistas em sua biblioteca musical, além disso você pode criar e editar suas próprias playlists personalizadas. Desfrute da moderna interface do GTK ou modifique o que quiser usando “skins” do Winamp Classic. Utilize os plug-ins inclusos no Audacious para encontrar letras de músicas, definir um alarme pela manhã e muito mais.
O Audacious funciona nos sistemas operacionais Linux, Windows e em algumas versões do BSD. Para baixar e instalar verifique a página de download.
Um processo pode ser dividido em diversas partes para se obter um melhor desempenho de processamento, pois se um processo estiver sendo executado de maneira individual ele será processado individualmente. Porém um mesmo processo comum pode ser dividido em diversas processos sendo cada uma das threads um componente do mesmo, tendo cada uma das partes processadas recebendo atenção do processador de maneira individual.
As threads podem resultar diversas vantagens para os programas e para o SO. Melhor capacidade de resposta, por ser mais rápida a criação de uma thread do que a de um novo processo. Threads de um mesmo processo possuem compartilhamento de recursos simplificado entre elas. Possuem uma economia com o uso de estruturas de controle reduzidas em comparação ao controle típico dos processos. E também o uso de compartilhamento de recursos simplificado também gera economia de outros recursos. Torna possível a exploração de diversas arquiteturas computacionais que possuem múltiplos processadores com uma maior adequabilidade.
Um processo pode ser dividido em diversas partes para se obter um melhor desempenho de processamento, pois se um processo estiver sendo executado de maneira individual ele será processado individualmente. Porém um mesmo processo comum pode ser dividido em diversas processos sendo cada uma das threads um componente do mesmo, tendo cada uma das partes processadas recebendo atenção do processador de maneira individual.
Cada processo utiliza um determinado intervalo de tempo de execução. Após esse intervalo, o processo é interrompido, para que outro processo seja iniciado dando a impressão que o computador está dedicado a um único processo. Já com a sincronização dos processos, podemos citar como exemplo, a execução de códigos que são executados em paralelo com outros. Isto permite que várias ações sejam executas em paralelo por um mesmo processo.
Porém existem alguns problemas com o compartilhamento de recursos: em toda a situação em que houver dois ou mais processos compartilhando um recurso, seja um arquivo ou uma área de memória, deve haver um mecanismo de controle para evitar estes conflitos de concorrência. Este mecanismo é conhecido por condição de corrida.
O programa Audacious faz uso de vários mecanismos para o controle de execução nos SO's, geralmente traz em suas linhas de códigos ferramentas como mecanismos de interrupções, exclusões mutuas e bloqueios de execução. Os códigos apresentados estarão atentados apenas para assuntos relevantes e específicos quanto ao uso de threads, funções de exclusões mútuas, monitores, semáforos e funções de sincronização presentes neste player de música.
Em várias etapas do código fonte do programa Audacious é possível encontrar diversas utilizações de threads, o que mostra que sua utilização traz grandes vantagens para o processamento das tarefas em execução, já que se trata de um player de áudio é extremamente necessário que exista uma sincronização para que exista qualidade na reprodução dos áudios. Abaixo está descrita uma das sequências de código que faz parte do pacote de instalação quando compilado para os sistemas operacionais do Windows que faz uso das chamadas threads.
Está condicional faz uso de uma função externa G_UNLIKELY que faz a comparação de valores lógicos, informando se o ponteiro init_thread é igual a g_thread_self( ) que também é uma função e retorna um valor para ser comparado. Na sequência temos esta parte do código que cuida também do funcionamento das threads em execução. A sequência acima define um ponteiro de caractere C padrão *tmp que recebe valores passados pela função externa _glib_get_locale_dir ( ) pertencente ao Win32 e será utilizado em alguns trechos do código para que o arquivo compilado seja padronizado para o SO que irá ser executado. A função g_atomic_pointer_set (&init_thread_atomic, g_thread_self ( )) é uma função que não deve ser interrompida no instante de execução e tem como finalidade manipular os valores passados por parâmetro do endereço de ponteiro de init_thread_atomic utilizando chamada por referência e o valor retornado pela função g_thread_self( ).
if (G_UNLIKELY (init_thread && init_thread == g_thread_self ()))
return FALSE;
if (g_once_init_enter (&initialised))
{
#ifdef G_OS_WIN32//se estiver definido no header será executada a sequência abaixo
gchar *tmp = _glib_get_locale_dir ();
gchar *tmp;
g_atomic_pointer_set (&init_thread_atomic, g_thread_self ());
tmp = _glib_get_locale_dir ();
bindtextdomain (GETTEXT_PACKAGE, tmp);
g_free (tmp);
g_atomic_pointer_set (&init_thread_atomic, NULL);
#else
bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
#endif
@@ -113,6 +128,8 @@
# endif
g_once_init_leave (&initialised, TRUE);
}
return TRUE;
}
É um mecanismo utilizado para a realização de exclusões mútuas as chamadas Mutex, que é muito utilizada nas programações concorrentes, ou seja, quando mais de um processo disputa informações ou dados em um sistema operacional. São usadas para que dois processos ou threads como neste caso, tenham acesso simultâneo a um recurso compartilhado, acesso esse conhecido como seção crítica. A variável pthread_mutex_t como vemos recebe como um valor passado por uma variável externa ao código que deverá ser definida durante a compilação, que se dará de acordo com o sistema operacional onde será executado A função apresentada acima status_cb() faz a verificação de status dos processos, utiliza uma condicional com a função aud_get_headless_mode () externa ao código em análise mas tem sua importância no funcionamento do aplicativo já que este faz uso da função fflush() para controle do buffer e a o stdout irá definir qual buffer será limpo, esta função faz parte da bibilioteca stdio.h, em caso de sucesso a função retorna o valor zero. É de grande importância observar que as funções status_cb() e status_update() contém em seu corpo as chamadas de funções pthread_mutex_lock() e pthread_mutex_unlock() que recebem como parâmetros o objeto referênciado pelo mutex a sere bloqueada. Quando chamada a função pthread_mutex_lock() é chamada se o objeto mutex já estiver bloqueado o segmento de chamada bloqueia até que o mutex se torne disponível, o que se dará depois do tempo de bloqueio definido na passagem de parâmetros para as funções as função pthread_mutex_unlock() de maneira análoga fará o desbloqueio do objeto mutex.
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER,
static void status_cb (void * unused)
{
pthread_mutex_lock (& mutex);
char scratch[128];
snprintf (scratch, sizeof scratch, dngettext (PACKAGE, "%d file found",
"%d files found", status_count), status_count);
if (aud_get_headless_mode ())
{
printf ("Searching, %s ...\r", scratch);
fflush (stdout);
}
else
{
hook_call ("ui show progress", status_path);
hook_call ("ui show progress 2", scratch);
}
status_shown = true;
pthread_mutex_unlock (& mutex);
}
static void status_update (const char * filename, int found)
{
pthread_mutex_lock (& mutex);
snprintf (status_path, sizeof status_path, "%s", filename);
status_count = found;
if (! status_timer.running ())
status_timer.start (250, status_cb, nullptr);
pthread_mutex_unlock (& mutex);
}
Uma análise breve deste código que é parte de uma código externo que será definido através do arquivo makefile já que recebe como inclusão as definições cue-cache.h, multihash.h e playlist-internal.h que irão ser unidas via inkeditor na compilação. A seguir a sequência de código, Nesta sequência de código tem como mecanismos de controle a variável mutex do tipo pthread_mutex_t para referenciar o objeto mutex e recebe o valor passado por PTHREAD_MUTEX_INITIALIZER externa ao código já definida no código de origem já incluido. Também é definida a variável cond do tipo pthread_cond_t que recebe como valor passado por PTHREAD_COND_INITIALIZER. O código faz uso da pthread_cond_broadcast função () que deve desbloquear os tópicos atualmente bloqueados na variável condição especificada em cond. Se bem-sucedida, a função pthread_cond_broadcast () deve retornar zero; caso contrário, um número de erro deve ser retornado para indicar o erro. A função pthread_cond_wait () sã ousadas para bloquear uma variável de condição. Eles sã ochamados com o mutex bloqueado pelo encadeamento de chamada ou resultará um comportamento indefinido. Essa função libera atomicamente o mutex e faz com que o encadeamento de chamada bloqueie na variável de condição cond ; atomicamente que significa "atomicamente em relação ao acesso por outro segmento ao mutex e depois à variável de condição". Exceto no caso de [ETIMEDOUT], todas essas verificações de erro agem como se fossem executadas imediatamente no início do processamento da função e causam um retorno de erro, com efeito, antes de modificar o estado do mutex especificado por mutex ou pelo variável de condição especificada por cond . Após a conclusão bem-sucedida, um valor de zero é retornado. Caso contrário, um número de erro é retornado para indicar o erro.
/*
* cue-cache.cc
* Copyright 2016 John Lindgren
*/
#include "cue-cache.h"
#include "multihash.h"
#include "playlist-internal.h"
#include
enum NodeState {NotLoaded, Loading, Loaded};
struct CueCacheNode {
Index items;
NodeState state = NotLoaded;
int refcount = 0;
};
static SimpleHash cache;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
CueCacheRef::CueCacheRef (const char * filename) :
m_filename (filename)
{
pthread_mutex_lock (& mutex);
m_node = cache.lookup (m_filename);
if (! m_node)
m_node = cache.add (m_filename, CueCacheNode ());
m_node->refcount ++;
pthread_mutex_unlock (& mutex);
}
CueCacheRef::~CueCacheRef ()
{
pthread_mutex_lock (& mutex);
m_node->refcount --;
if (! m_node->refcount)
cache.remove (m_filename);
pthread_mutex_unlock (& mutex);
}
const Index & CueCacheRef::load ()
{
String title; // not used
pthread_mutex_lock (& mutex);
switch (m_node->state)
{
case NotLoaded:
// load the cuesheet in this thread
m_node->state = Loading;
pthread_mutex_unlock (& mutex);
playlist_load (m_filename, title, m_node->items);
pthread_mutex_lock (& mutex);
m_node->state = Loaded;
pthread_cond_broadcast (& cond);
break;
case Loading:
// wait for cuesheet to load in another thread
while (m_node->state != Loaded)
pthread_cond_wait (& cond, & mutex);
break;
case Loaded:
// cuesheet already loaded
break;
}
pthread_mutex_unlock (& mutex);
return m_node->items;
}
Mainloop é uma camada de abstração de loop principal que pode usar GLib ou Qt como backend. A API é completamente thread-safe e, portanto, pode ser usada como um meio de chamar de volta para o segmento principal da execução de uma thread. A variável externa LIBAUDCORE_MAINLOOP_H é uma definição e para isto utiliza as #ifndef e #define para sua utilização e das funções e procedimentos que a compõe. A Diretiva ifndef A diretiva #ifndef funciona ao contrário da diretiva #ifdef A sequência de declarações será compilada se o nome da macro não tiver sido definido. A classe QueuedFunc utiliza o comando typedef void (* Func) (void * data), que faz o retorno de chamada ocioso de uma só vez e neste caso o typedef define um ponteiro de funções. Como um simples exemplo o typedef pode ser usado para definir um novo nome para um determinado tipo
#ifndef LIBAUDCORE_MAINLOOP_H
#define LIBAUDCORE_MAINLOOP_H
class QueuedFunc
{
public:
typedef void (* Func) (void * data);
// one-time idle callback
void queue (Func func, void * data);
// one-time delayed callback
void queue (int delay_ms, Func func, void * data);
// periodic timer callback
void start (int interval_ms, Func func, void * data);
// stops any type of callback
// note that queue() and start() also stop any previous callback
void stop ();
// true if a periodic timer is running
// does not apply to one-time callbacks
bool running () const
{
return _running;
}
constexpr QueuedFunc () = default;
QueuedFunc (const QueuedFunc &) = delete;
void operator= (const QueuedFunc &) = delete;
~QueuedFunc ()
{
stop ();
}
// cancels any pending callbacks
// inhibits all future callbacks
// needed to allow safe shutdown of some (Qt!) main loops
static void inhibit_all ();
private:
bool _running = false;
};
void mainloop_run ();
void mainloop_quit ();
#endif
Desde o Audacious 3.6, o encadeamento de playback é completamente assíncrono; isso é, a thread principal nunca bloqueia a espera para o segmento de reprodução para processar um comando play (), seek () ou stop (). Como resultado, o encadeamento de reprodução pode atrasar por trás do tópico principal / playlist, e a música "atual" da perspectiva do segmento de reprodução pode não ser o mesmo que a música "atual" da perspectiva do segmento principal / playlist. Portanto, alguns cuidados são necessários para garantir que as informações geradas no encadeamento de reprodução não sejam aplicadas à música errada. Para este fim, números de série separados são mantidos por cada thread e comparados quando a informação ultrapassa os limites das threads; se os números séries não coincidem, a informação é geralmente descartada. Note que este arquivo e o playlist.cc possuem seu próprio mutex. O playlist.cc é conceitualmente o mutex "externo" e deve ser bloqueado primeiro (em situações que ambos precisam ser bloqueados) para evitar o impasse.
/*
playback.cc
Copyright 2009-2014 John Lindgren
*/
#include "drct.h"
#include "internal.h"
#include
#include
#include "audstrings.h"
#include "hook.h"
#include "i18n.h"
#include "interface.h"
#include "mainloop.h"
#include "output.h"
#include "playlist-internal.h"
#include "plugin.h"
#include "plugins.h"
#include "plugins-internal.h"
#include "runtime.h"
struct PlaybackState {
bool playing = false;
bool thread_running = false;
int control_serial = 0;
int playback_serial = 0;
};
struct PlaybackControl {
bool paused = false;
int seek = -1;
int repeat_a = -1;
int repeat_b = -1;
};
struct PlaybackInfo {
// set by playback_set_info
int entry = -1;
Tuple tuple;
String title;
// set by playback thread
String filename;
int length = -1;
int time_offset = 0;
int stop_time = -1;
ReplayGainInfo gain {};
bool gain_valid = false;
int bitrate = 0;
int samplerate = 0;
int channels = 0;
bool ready = false;
bool ended = false;
bool error = false;
String error_s;
};
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static PlaybackState pb_state;
static PlaybackControl pb_control;
static PlaybackInfo pb_info;
static QueuedFunc end_queue;
static bool song_finished = false;
static int failed_entries = 0;
static void lock ()
{
pthread_mutex_lock (& mutex);
}
static void unlock ()
{
pthread_mutex_unlock (& mutex);
}
static bool lock_if (bool (* test) ())
{
lock ();
if (test ())
return true;
unlock ();
return false;
}
// verifica se o encadeamento da reprodução não está atrasado
static bool in_sync ()
{
return pb_state.playing && pb_state.control_serial == pb_state.playback_serial;
}
// verifica se o encadeamento de reprodução não está atrasado e se a reprodução está "pronta"
static bool is_ready ()
{
return in_sync () && pb_info.ready;
}
// chamado por playback_entry_set_tuple () para garantir que a tupla ainda se aplique
// para a música atual da perspectiva do segmento principal / playlist; a
// check é necessário porque playback_entry_set_tuple () é ele mesmo chamado de
// o segmento de reprodução
bool playback_check_serial (int serial)
{
lock ();
bool okay = (pb_state.playing && pb_state.control_serial == serial);
unlock ();
return okay;
}
// chamado da lista de reprodução para atualizar a tupla da música atual
void playback_set_info (int entry, Tuple && tuple)
{
// não faz nada se o segmento de reprodução estiver atrasado;
// Nesse caso, playback_set_info () será chamado novamente de qualquer maneira
if (! lock_if (in_sync))
return;
if (tuple.valid () && tuple != pb_info.tuple)
{
pb_info.tuple = std::move (tuple);
// não chame "tuple change" antes de "reprodução pronta"
if (is_ready ())
{
event_queue ("tuple change", nullptr);
output_set_tuple (pb_info.tuple);
}
}
String title = pb_info.tuple.get_str (Tuple::FormattedTitle);
if (entry != pb_info.entry || title != pb_info.title)
{
pb_info.entry = entry;
pb_info.title = title;
// não chame "title change" antes de "playback ready"
if (is_ready ())
event_queue ("title change", nullptr);
}
unlock ();
}
// limpeza comum para playback_play () e playback_stop ()
static void playback_cleanup_locked ()
{
pb_state.playing = false;
pb_control = PlaybackControl ();
// descarte o buffer de áudio se a música não terminar sozinha
if (! song_finished)
output_flush (0);
// limpeza alternada
end_queue.stop ();
song_finished = false;
event_queue_cancel ("playback ready");
event_queue_cancel ("playback pause");
event_queue_cancel ("playback unpause");
event_queue_cancel ("playback seek");
event_queue_cancel ("info change");
event_queue_cancel ("title change");
event_queue_cancel ("tuple change");
aud_set_bool (nullptr, "stop_after_current_song", false);
}
// main thread: interrompe a reprodução quando não é mais necessário tocar músicas void playback_stop (bool exiting)
{
if (! pb_state.playing && ! exiting)
return;
lock ();
if (pb_state.playing)
playback_cleanup_locked ();
if (pb_state.thread_running)
{
// se sair descarte o áudio do buffer
if (exiting)
output_flush (0, true);
// thread de reprodução de sinal para drenar o buffer de áudio
pb_state.control_serial ++;
pthread_cond_broadcast (& cond);
// aguarde até que o encadeamento de reprodução termine se sair
while (exiting && pb_state.thread_running)
pthread_cond_wait (& cond, & mutex);
}
unlock ();
// limpeza alternada
failed_entries = 0;
}
// chamada do loop de evento de nível superior após a reprodução terminar
static void end_cb (void *)
{
song_finished = true;
hook_call ("playback end", nullptr);
PlaylistEx playlist = Playlist::playing_playlist ();
auto do_stop = [playlist] ()
{
aud_drct_stop ();
playlist.set_position (playlist.get_position ());
};
auto do_next = [playlist] ()
{
if (! playlist.next_song (aud_get_bool (nullptr, "repeat")))
{
playlist.set_position (-1);
hook_call ("playlist end reached", nullptr);
}
};
if (aud_get_bool (nullptr, "no_playlist_advance"))
{
// assumimos aqui que a repetição não está habilitada;
// repetições de uma única música são tratadas em run_playback ()
do_stop ();
}
else if (aud_get_bool (nullptr, "stop_after_current_song"))
{
do_stop ();
do_next ();
}
else
{
// se 10 músicas em sequência falharem ou se falhou a playlist inteira
// (para playlists com menos de 10 músicas) falhou, pare de tentar
if (failed_entries < aud::min (playlist.n_entries (), 10))
do_next ();
else
do_stop ();
}
}
// helper, pode ser chamado a partir do thread principal ou de reprodução
static void request_seek_locked (int time)
{
// configurar o comando "seek", esteja pronto ou não;
//se não estiver pronto, entrará em vigor após open_audio ()
pb_control.seek = aud::max (0, time);
// trigger seek imediatamente se estiver pronto
if (is_ready () && pb_info.length > 0)
{
output_flush (aud::clamp (time, 0, pb_info.length));
event_queue ("playback seek", nullptr);
}
}
// ajudante de segmento de reprodução de thread
static void run_playback ()
{
// devido ao pedido de mutex, não podemos chamar a playlist enquanto estiver bloqueada
DecodeInfo dec = playback_entry_read (pb_state.playback_serial);
if (! lock_if (in_sync))
return;
// para uma entrada de cuesheet, determine o nome do arquivo de origem
pb_info.filename = pb_info.tuple.get_str (Tuple::AudioFile);
if (! pb_info.filename)
pb_info.filename = std::move (dec.filename);
// verifique se possui todos os dados necessários
if (! pb_info.filename || ! pb_info.tuple.valid () || ! dec.ip ||
(! dec.ip->input_info.keys[InputKey::Scheme] && ! dec.file))
{
pb_info.error = true;
pb_info.error_s = std::move (dec.error);
unlock ();
return;
}
// Obter vários outros bits de informação da tupla
pb_info.length = pb_info.tuple.get_int (Tuple::Length);
pb_info.time_offset = aud::max (0, pb_info.tuple.get_int (Tuple::StartTime));
pb_info.stop_time = aud::max (-1, pb_info.tuple.get_int (Tuple::EndTime) - pb_info.time_offset);
pb_info.gain = pb_info.tuple.get_replay_gain ();
pb_info.gain_valid = pb_info.tuple.has_replay_gain ();
// força inicial busca se estes estão tocando uma faixa segmentada
if (pb_info.time_offset > 0 && pb_control.seek < 0)
pb_control.seek = 0;
unlock ();
while (1)
{
// // forçar a busca inicial se estivermos jogando um controle segmentado fora do controle para inserir o plugin
if (! dec.ip->play (pb_info.filename, dec.file))
pb_info.error = true;
// close audio (não abra se este estiver fechado)
output_close_audio ();
if (pb_info.error || pb_info.length <= 0)
break;
if (! lock_if (in_sync))
break;
// verifique se o audio precisa ser repetido
pb_info.ended = (pb_control.repeat_a < 0 && ! (aud_get_bool (nullptr,
"repeat") && aud_get_bool (nullptr, "no_playlist_advance")));
if (! pb_info.ended)
request_seek_locked (pb_control.repeat_a);
unlock ();
if (pb_info.ended)
break;
// retorne o ponteiro do arquivo para repetir
if (! open_input_file (pb_info.filename, "r", dec.ip, dec.file, & pb_info.error_s))
{
pb_info.error = true;
break;
}
}
}
// ajudante da thread de reprodução
static void finish_playback_locked ()
{
// registrar qualquer erro de execução ocorrido
if (pb_info.error)
{
failed_entries ++;
if (pb_info.error_s)
aud_ui_show_error (str_printf (_("Error playing %s:\n%s"),
(const char *) pb_info.filename, (const char *) pb_info.error_s));
else
AUDERR ("Playback finished with error.\n");
}
else
failed_entries = 0;
// queue chama função para iniciar a próxima música (ou realizar limpeza)
end_queue.queue (end_cb, nullptr);
}
// thread de reprodução
static void * playback_thread (void *)
{
lock ();
while (1)
{
// aguardando por um comando
while (pb_state.control_serial == pb_state.playback_serial)
pthread_cond_wait (& cond, & mutex);
// busque o comando ("play" ou "drain")
bool play = pb_state.playing;
// atualiza o número série da thread de execução
pb_state.playback_serial = pb_state.control_serial;
unlock ();
if (play)
run_playback ();
else
output_drain ();
lock ();
if (play)
{
// não reportar erros ou enfileirar a próxima música se outro comando estiver pendente
if (in_sync ())
finish_playback_locked ();
pb_info = PlaybackInfo ();
}
else
{
// sair se não tiver recebido um novo comando depois da drenagem
if (pb_state.control_serial == pb_state.playback_serial)
break;
}
}
// sinalize a thread principal que estará saindo
pb_state.thread_running = false;
pthread_cond_broadcast (& cond);
unlock ();
return nullptr;
}
// main thread: iniciando a reprodução de uma nova música
void playback_play (int seek_time, bool pause)
{
lock ();
if (pb_state.playing)
playback_cleanup_locked ();
// ativando o comando "play"
pb_state.playing = true;
pb_state.control_serial ++;
pb_control.paused = pause;
pb_control.seek = (seek_time > 0) ? seek_time : -1;
// inicia thread de execução (ou sinalize se já estiver rodando)
if (pb_state.thread_running)
pthread_cond_broadcast (& cond);
else
{
pthread_t thread;
pthread_create (& thread, nullptr, playback_thread, nullptr);
pthread_detach (thread);
pb_state.thread_running = true;
}
unlock ();
}
// main thread
EXPORT void aud_drct_pause ()
{
if (! pb_state.playing)
return;
lock ();
// ativando o comando "pause" esteja pronto ou não;
// se não estiver pronto, este entrará em vigor após open_audio()
bool pause = ! pb_control.paused;
pb_control.paused = pause;
// aplique pause imediatamente se estiver executando
if (is_ready ())
output_pause (pause);
event_queue (pause ? "playback pause" : "playback unpause", nullptr);
unlock ();
}
// main thread
EXPORT void aud_drct_seek (int time)
{
if (! pb_state.playing)
return;
lock ();
request_seek_locked (time);
unlock ();
}
EXPORT void InputPlugin::open_audio (int format, int rate, int channels)
{
// não abra o áudio se a thread de execução estiver atrasada
if (! lock_if (in_sync))
return;
if (! output_open_audio (pb_info.filename, pb_info.tuple, format, rate,
channels, aud::max (0, pb_control.seek)))
{
pb_info.error = true;
pb_info.error_s = String (_("Invalid audio format"));
unlock ();
return;
}
if (pb_info.gain_valid)
output_set_replay_gain (pb_info.gain);
if (pb_control.paused)
output_pause (true);
pb_info.samplerate = rate;
pb_info.channels = channels;
if (pb_info.ready)
event_queue ("info change", nullptr);
else
event_queue ("playback ready", nullptr);
pb_info.ready = true;
unlock ();
}
EXPORT void InputPlugin::set_replay_gain (const ReplayGainInfo & gain)
{
lock ();
pb_info.gain = gain;
pb_info.gain_valid = true;
if (is_ready ())
output_set_replay_gain (gain);
unlock ();
}
EXPORT void InputPlugin::write_audio (const void * data, int length)
{
if (! lock_if (in_sync))
return;
// buscar configuração A-B
int a = pb_control.repeat_a;
int b = pb_control.repeat_b;
unlock ();
// estes estarão prontos para chamar output_write_audio() mesmo que não estiver mais em sincronia,
// já que retornará imediatamente se output_flush () tiver sido chamado
int stop_time = (b >= 0) ? b : pb_info.stop_time;
if (output_write_audio (data, length, stop_time))
return;
if (! lock_if (in_sync))
return;
// se estiverem em sincronia, então ocorreu uma das seguintes situações:
// 1. output_flush() foi chamado devido a um pedido de busca
// 2. nós alcançamos o ponto de repetição B
// 3. nós chegamos ao final de um segmento
if (pb_control.seek < 0)
{
if (b >= 0)
request_seek_locked (a);
else
pb_info.ended = true;
}
unlock ();
}
EXPORT Tuple InputPlugin::get_playback_tuple ()
{
lock ();
Tuple tuple = pb_info.tuple.ref ();
unlock ();
// tuplas passadas de plugins de entrada não possuem campos de fallback
// gerado; para consistência, as tuplas devolvidas não devem ser passadas
tuple.delete_fallbacks ();
return tuple;
}
EXPORT void InputPlugin::set_playback_tuple (Tuple && tuple)
{
// devido à ordenação por mutex, não podemos chamar a playlist enquanto estiver bloqueada;
// em vez disso, playback_entry_set_tuple () chama de volta para o primeiro
// playback_check_serial () e depois playback_set_info ()
playback_entry_set_tuple (pb_state.playback_serial, std::move (tuple));
}
EXPORT void InputPlugin::set_stream_bitrate (int bitrate)
{
lock ();
pb_info.bitrate = bitrate;
if (is_ready ())
event_queue ("info change", nullptr);
unlock ();
}
EXPORT bool InputPlugin::check_stop ()
{
lock ();
bool stop = ! is_ready () || pb_info.ended || pb_info.error;
unlock ();
return stop;
}
EXPORT int InputPlugin::check_seek ()
{
lock ();
int seek = -1;
if (is_ready () && pb_control.seek >= 0 && pb_info.length > 0)
{
seek = pb_info.time_offset + aud::min (pb_control.seek, pb_info.length);
pb_control.seek = -1;
output_resume ();
}
unlock ();
return seek;
}
// thread de proteção
EXPORT bool aud_drct_get_playing ()
{
lock ();
bool playing = pb_state.playing;
unlock ();
return playing;
}
// thread de proteção
EXPORT bool aud_drct_get_ready ()
{
lock ();
bool ready = is_ready ();
unlock ();
return ready;
}
// thread de proteção
EXPORT bool aud_drct_get_paused ()
{
lock ();
bool paused = pb_control.paused;
unlock ();
return paused;
}
// thread de proteção
EXPORT String aud_drct_get_title ()
{
if (! lock_if (is_ready))
return String ();
String title = pb_info.title;
int entry = pb_info.entry;
int length = pb_info.length;
unlock ();
StringBuf prefix = aud_get_bool (nullptr, "show_numbers_in_pl") ?
str_printf ("%d. ", 1 + entry) : StringBuf (0);
StringBuf time = (length > 0) ? str_format_time (length) : StringBuf ();
StringBuf suffix = time ? str_concat ({" (", time, ")"}) : StringBuf (0);
return String (str_concat ({prefix, title, suffix}));
}
// thread de proteção
EXPORT Tuple aud_drct_get_tuple ()
{
lock ();
Tuple tuple = is_ready () ? pb_info.tuple.ref () : Tuple ();
unlock ();
return tuple;
}
// thread de proteção
EXPORT void aud_drct_get_info (int & bitrate, int & samplerate, int & channels)
{
lock ();
bool ready = is_ready ();
bitrate = ready ? pb_info.bitrate : 0;
samplerate = ready ? pb_info.samplerate : 0;
channels = ready ? pb_info.channels : 0;
unlock ();
}
// thread de proteção
EXPORT int aud_drct_get_time ()
{
lock ();
int time = is_ready () ? output_get_time () : 0;
unlock ();
return time;
}
// thread de proteção
EXPORT int aud_drct_get_length ()
{
lock ();
int length = is_ready () ? pb_info.length : -1;
unlock ();
return length;
}
// thread de proteção
EXPORT void aud_drct_set_ab_repeat (int a, int b)
{
if (! pb_state.playing)
return;
lock ();
pb_control.repeat_a = a;
pb_control.repeat_b = b;
if (b >= 0 && is_ready () && output_get_time () >= b)
request_seek_locked (a);
unlock ();
}
// thread de proteção
EXPORT void aud_drct_get_ab_repeat (int & a, int & b)
{
lock ();
a = pb_control.repeat_a;
b = pb_control.repeat_b;
unlock ();}
Os repositórios de plugins audaciouos estão hospedados no GitHub.