2011-04-26 22:53:19 +02:00
|
|
|
// Copyright (c) 2011, Niels Martin Hansen
|
2007-04-22 23:59:35 +02:00
|
|
|
// 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 Aegisub Group nor the names of its 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.
|
|
|
|
//
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// AEGISUB
|
|
|
|
//
|
2011-04-26 22:53:19 +02:00
|
|
|
// Website: http://www.aegisub.org
|
|
|
|
// Contact: mailto:nielsm@indvikleren.dk
|
2007-04-22 23:59:35 +02:00
|
|
|
//
|
|
|
|
|
|
|
|
|
2009-01-04 07:31:48 +01:00
|
|
|
#include "config.h"
|
|
|
|
|
2008-01-21 21:57:20 +01:00
|
|
|
#ifdef WITH_ALSA
|
|
|
|
|
|
|
|
|
2007-04-22 23:59:35 +02:00
|
|
|
///////////
|
|
|
|
// Headers
|
2011-04-26 22:53:19 +02:00
|
|
|
#include "audio_player_alsa.h"
|
|
|
|
#include "include/aegisub/audio_provider.h"
|
|
|
|
|
|
|
|
#include <alsa/asoundlib.h>
|
|
|
|
|
2007-04-22 23:59:35 +02:00
|
|
|
#include <wx/wxprec.h>
|
|
|
|
#include "utils.h"
|
|
|
|
#include "frame_main.h"
|
2007-04-23 19:01:00 +02:00
|
|
|
#include "options.h"
|
2011-04-26 22:53:19 +02:00
|
|
|
#include <algorithm>
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
class PthreadMutexLocker {
|
|
|
|
pthread_mutex_t &mutex;
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
PthreadMutexLocker(const PthreadMutexLocker &); // uncopyable
|
|
|
|
PthreadMutexLocker(); // no default
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
public:
|
|
|
|
explicit PthreadMutexLocker(pthread_mutex_t &mutex) : mutex(mutex)
|
|
|
|
{
|
|
|
|
pthread_mutex_lock(&mutex);
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
~PthreadMutexLocker()
|
|
|
|
{
|
|
|
|
pthread_mutex_unlock(&mutex);
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
int WaitCondition(pthread_cond_t &cond)
|
|
|
|
{
|
|
|
|
return pthread_cond_wait(&cond, &mutex);
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
int WaitConditionTimeout(pthread_cond_t &cond, int ms)
|
|
|
|
{
|
|
|
|
timespec abstime;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &abstime);
|
|
|
|
abstime.tv_nsec += ms * 1000000;
|
2011-04-27 10:48:01 +02:00
|
|
|
abstime.tv_sec += abstime.tv_nsec / 1000000000;
|
|
|
|
abstime.tv_nsec = abstime.tv_nsec % 1000000000;
|
2011-04-26 22:53:19 +02:00
|
|
|
return pthread_cond_timedwait(&cond, &mutex, &abstime);
|
|
|
|
}
|
|
|
|
};
|
2007-04-22 23:59:35 +02:00
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
class ScopedAliveFlag {
|
|
|
|
volatile bool &flag;
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
ScopedAliveFlag(const ScopedAliveFlag &); // uncopyable
|
|
|
|
ScopedAliveFlag(); // no default
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
public:
|
|
|
|
explicit ScopedAliveFlag(volatile bool &var) : flag(var) { flag = true; }
|
|
|
|
~ScopedAliveFlag() { flag = false; }
|
|
|
|
};
|
2007-04-22 23:59:35 +02:00
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
struct PlaybackState {
|
|
|
|
pthread_mutex_t mutex;
|
|
|
|
pthread_cond_t cond;
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
volatile bool playing;
|
|
|
|
volatile bool alive;
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
volatile bool signal_start;
|
|
|
|
volatile bool signal_stop;
|
|
|
|
volatile bool signal_close;
|
|
|
|
volatile bool signal_volume;
|
2007-04-26 20:34:36 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
volatile double volume;
|
|
|
|
volatile int64_t start_position;
|
|
|
|
volatile int64_t end_position;
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
AudioProvider *provider;
|
|
|
|
std::string device_name;
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
int64_t last_position;
|
|
|
|
timespec last_position_time;
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
PlaybackState()
|
|
|
|
{
|
|
|
|
pthread_mutex_init(&mutex, 0);
|
|
|
|
pthread_cond_init(&cond, 0);
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
Reset();
|
|
|
|
volume = 1.0;
|
2007-04-26 22:11:48 +02:00
|
|
|
}
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
~PlaybackState()
|
|
|
|
{
|
|
|
|
pthread_cond_destroy(&cond);
|
|
|
|
pthread_mutex_destroy(&mutex);
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
void Reset()
|
|
|
|
{
|
|
|
|
playing = false;
|
|
|
|
alive = false;
|
|
|
|
signal_start = false;
|
|
|
|
signal_stop = false;
|
|
|
|
signal_close = false;
|
|
|
|
signal_volume = false;
|
|
|
|
start_position = 0;
|
|
|
|
end_position = 0;
|
|
|
|
last_position = 0;
|
|
|
|
provider = 0;
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
2011-04-26 22:53:19 +02:00
|
|
|
};
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
class AlsaPlayer : public AudioPlayer {
|
|
|
|
private:
|
|
|
|
PlaybackState ps;
|
|
|
|
pthread_t thread;
|
2011-04-27 10:48:01 +02:00
|
|
|
|
|
|
|
bool IsAlive();
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
public:
|
|
|
|
AlsaPlayer();
|
|
|
|
~AlsaPlayer();
|
|
|
|
|
|
|
|
void OpenStream();
|
|
|
|
void CloseStream();
|
|
|
|
|
|
|
|
void Play(int64_t start, int64_t count);
|
|
|
|
void Stop(bool timerToo=true);
|
|
|
|
bool IsPlaying();
|
|
|
|
|
|
|
|
int64_t GetStartPosition();
|
|
|
|
int64_t GetEndPosition();
|
|
|
|
int64_t GetCurrentPosition();
|
|
|
|
void SetEndPosition(int64_t pos);
|
|
|
|
void SetCurrentPosition(int64_t pos);
|
|
|
|
|
|
|
|
void SetVolume(double vol);
|
|
|
|
double GetVolume();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void *playback_thread(void *arg)
|
|
|
|
{
|
|
|
|
// This is exception-free territory!
|
|
|
|
// Return a pointer to a static string constant describing the error, or 0 on no error
|
|
|
|
PlaybackState &ps = *(PlaybackState*)arg;
|
|
|
|
|
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
ScopedAliveFlag alive_flag(ps.alive);
|
|
|
|
|
|
|
|
snd_pcm_t *pcm = 0;
|
|
|
|
if (snd_pcm_open(&pcm, ps.device_name.c_str(), SND_PCM_STREAM_PLAYBACK, 0) != 0)
|
|
|
|
return "snd_pcm_open";
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: opened pcm\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
do_setup:
|
|
|
|
snd_pcm_format_t pcm_format;
|
|
|
|
switch (ps.provider->GetBytesPerSample())
|
|
|
|
{
|
|
|
|
case 1:
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: format U8\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
pcm_format = SND_PCM_FORMAT_U8;
|
|
|
|
break;
|
|
|
|
case 2:
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: format S16_LE\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
pcm_format = SND_PCM_FORMAT_S16_LE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
snd_pcm_close(pcm);
|
|
|
|
return "snd_pcm_format_t";
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
2011-04-26 22:53:19 +02:00
|
|
|
if (snd_pcm_set_params(pcm,
|
|
|
|
pcm_format,
|
|
|
|
SND_PCM_ACCESS_RW_INTERLEAVED,
|
|
|
|
ps.provider->GetChannels(),
|
|
|
|
ps.provider->GetSampleRate(),
|
|
|
|
1, // allow resample
|
|
|
|
100*1000 // 100 milliseconds latency
|
|
|
|
) != 0)
|
|
|
|
return "snd_pcm_set_params";
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: set pcm params\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
size_t framesize = ps.provider->GetChannels() * ps.provider->GetBytesPerSample();
|
|
|
|
|
|
|
|
ps.signal_close = false;
|
|
|
|
while (ps.signal_close == false)
|
|
|
|
{
|
|
|
|
// Wait for condition to trigger
|
|
|
|
if (!ps.signal_start)
|
|
|
|
ml.WaitCondition(ps.cond);
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: outer loop, condition happened\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
if (ps.signal_start == false || ps.end_position <= ps.start_position)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: starting playback\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
int64_t position = ps.start_position;
|
|
|
|
|
|
|
|
// Playback position
|
|
|
|
ps.last_position = position;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ps.last_position_time);
|
|
|
|
|
|
|
|
// Initial buffer-fill
|
|
|
|
snd_pcm_sframes_t avail = std::min(snd_pcm_avail(pcm), (snd_pcm_sframes_t)(ps.end_position-position));
|
|
|
|
char *buf = new char[avail*framesize];
|
|
|
|
ps.provider->GetAudioWithVolume(buf, position, avail, ps.volume);
|
|
|
|
snd_pcm_sframes_t written = 0;
|
|
|
|
while (written <= 0)
|
|
|
|
{
|
|
|
|
written = snd_pcm_writei(pcm, buf, avail);
|
|
|
|
if (written == -ESTRPIPE)
|
|
|
|
{
|
|
|
|
snd_pcm_recover(pcm, written, 0);
|
|
|
|
}
|
|
|
|
else if (written <= 0)
|
|
|
|
{
|
|
|
|
delete[] buf;
|
|
|
|
snd_pcm_close(pcm);
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: error filling buffer\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
return "snd_pcm_writei";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete[] buf;
|
|
|
|
position += written;
|
|
|
|
|
|
|
|
// Start playback
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: initial buffer filled, hitting start\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
snd_pcm_start(pcm);
|
|
|
|
|
|
|
|
ps.signal_start = false;
|
|
|
|
ps.signal_stop = false;
|
|
|
|
|
|
|
|
while (ps.signal_stop == false)
|
|
|
|
{
|
|
|
|
ScopedAliveFlag playing_flag(ps.playing);
|
|
|
|
|
|
|
|
// Sleep a bit, or until an event
|
|
|
|
ml.WaitConditionTimeout(ps.cond, 50);
|
|
|
|
//printf("alsa_player: playback loop, out of wait\n");
|
|
|
|
|
|
|
|
// Check for stop signal
|
|
|
|
if (ps.signal_stop == true)
|
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: playback loop, stop signal\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
snd_pcm_drop(pcm);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Playback position
|
|
|
|
snd_pcm_sframes_t delay;
|
|
|
|
if (snd_pcm_delay(pcm, &delay) == 0)
|
|
|
|
{
|
|
|
|
ps.last_position = position - delay;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ps.last_position_time);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill buffer
|
|
|
|
avail = std::min(snd_pcm_avail(pcm), (snd_pcm_sframes_t)(ps.end_position-position));
|
|
|
|
buf = new char[avail*framesize];
|
|
|
|
ps.provider->GetAudioWithVolume(buf, position, avail, ps.volume);
|
|
|
|
written = 0;
|
|
|
|
while (written <= 0)
|
|
|
|
{
|
|
|
|
written = snd_pcm_writei(pcm, buf, avail);
|
|
|
|
if (written == -ESTRPIPE || written == -EPIPE)
|
|
|
|
{
|
|
|
|
snd_pcm_recover(pcm, written, 0);
|
|
|
|
}
|
|
|
|
else if (written == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (written < 0)
|
|
|
|
{
|
|
|
|
delete[] buf;
|
|
|
|
snd_pcm_close(pcm);
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: error filling buffer, written=%d\n", written);
|
2011-04-26 22:53:19 +02:00
|
|
|
return "snd_pcm_writei";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete[] buf;
|
|
|
|
position += written;
|
|
|
|
//printf("alsa_player: playback loop, filled buffer\n");
|
|
|
|
|
|
|
|
// Check for end of playback
|
|
|
|
if (position >= ps.end_position)
|
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: playback loop, past end, draining\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
snd_pcm_drain(pcm);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ps.signal_stop = false;
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: out of playback loop\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
switch (snd_pcm_state(pcm))
|
|
|
|
{
|
|
|
|
case SND_PCM_STATE_OPEN:
|
|
|
|
// no clue what could have happened here, but start over
|
|
|
|
ps.signal_start = false;
|
|
|
|
ps.signal_stop = false;
|
|
|
|
goto do_setup;
|
|
|
|
|
|
|
|
case SND_PCM_STATE_SETUP:
|
|
|
|
// we lost the preparedness?
|
|
|
|
snd_pcm_prepare(pcm);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_PCM_STATE_DISCONNECTED:
|
|
|
|
// lost device, close the handle and return error
|
|
|
|
snd_pcm_close(pcm);
|
|
|
|
return "SND_PCM_STATE_DISCONNECTED";
|
|
|
|
|
|
|
|
default:
|
|
|
|
// everything else should either be fine or impossible (here)
|
|
|
|
break;
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
2011-04-26 22:53:19 +02:00
|
|
|
ps.signal_close = false;
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("alsa_player: out of outer loop\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
snd_pcm_close(pcm);
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
return 0;
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
AlsaPlayer::AlsaPlayer()
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-26 22:53:19 +02:00
|
|
|
ps.Reset();
|
2011-04-27 10:48:01 +02:00
|
|
|
thread = 0;
|
2011-04-26 22:53:19 +02:00
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
AlsaPlayer::~AlsaPlayer()
|
|
|
|
{
|
|
|
|
CloseStream();
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
void AlsaPlayer::OpenStream()
|
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
// Don't re-open if it's already alive
|
|
|
|
// Assume this means it isn't about to shut down
|
|
|
|
if (IsAlive()) return;
|
2011-04-26 22:53:19 +02:00
|
|
|
|
2011-04-27 10:48:01 +02:00
|
|
|
// Catch any errors in case the thread died unexpectedly
|
2011-04-26 22:53:19 +02:00
|
|
|
CloseStream();
|
2007-04-22 23:59:35 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
ps.Reset();
|
|
|
|
ps.provider = GetProvider();
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
wxString device_name = Options.AsText(_T("Audio ALSA Device"));
|
|
|
|
ps.device_name = std::string(device_name.utf8_str());
|
2010-01-16 00:01:36 +01:00
|
|
|
|
2011-04-27 10:48:01 +02:00
|
|
|
int err = pthread_create(&thread, 0, &playback_thread, &ps);
|
|
|
|
|
|
|
|
if (err == EAGAIN)
|
2011-04-26 22:53:19 +02:00
|
|
|
{
|
2011-04-27 11:01:21 +02:00
|
|
|
wxLogError(_T("AlsaPlayer: Failed creating playback thread, too little resources"));
|
2011-04-26 22:53:19 +02:00
|
|
|
}
|
2011-04-27 10:48:01 +02:00
|
|
|
else if (err == EPERM)
|
2011-04-26 22:53:19 +02:00
|
|
|
{
|
2011-04-27 11:01:21 +02:00
|
|
|
wxLogError(_T("AlsaPlayer: Failed creating playback thread, permissions error"));
|
2011-04-27 10:48:01 +02:00
|
|
|
}
|
|
|
|
else if (err != 0)
|
|
|
|
{
|
2011-04-27 11:01:21 +02:00
|
|
|
wxLogError(_T("AlsaPlayer: Failed creating playback thread, unexpected error, report this to the developers"));
|
2007-04-25 00:29:27 +02:00
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-25 00:29:27 +02:00
|
|
|
void AlsaPlayer::CloseStream()
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
// Make the thread go away if it's alive
|
|
|
|
if (IsAlive())
|
2011-04-26 22:53:19 +02:00
|
|
|
{
|
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
ps.signal_stop = true;
|
|
|
|
ps.signal_close = true;
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("AlsaPlayer: close stream, stop+close signal\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
pthread_cond_signal(&ps.cond);
|
|
|
|
}
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-27 10:48:01 +02:00
|
|
|
if (thread == 0) return;
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-27 10:48:01 +02:00
|
|
|
void *thread_result;
|
|
|
|
int err = pthread_join(thread, &thread_result);
|
|
|
|
thread = 0;
|
|
|
|
|
|
|
|
if (err == 0 && thread_result != 0)
|
|
|
|
{
|
|
|
|
// Thread errored
|
|
|
|
wxString errstr((const char *)thread_result, wxConvUTF8);
|
2011-04-27 11:01:21 +02:00
|
|
|
if (errstr == _T("snd_pcm_open"))
|
|
|
|
errstr = _T("Could not open the specified PCM device");
|
|
|
|
else if (errstr == _T("snd_pcm_format_t"))
|
|
|
|
errstr = _T("Unsupported sample format");
|
|
|
|
else if (errstr == _T("snd_pcm_set_params"))
|
|
|
|
errstr = _T("Could not set stream format");
|
|
|
|
else if (errstr == _T("snd_pcm_writei"))
|
|
|
|
errstr = _T("Error sending audio data to device");
|
|
|
|
else if (errstr == _T("SND_PCM_STATE_DISCONNECTED"))
|
|
|
|
errstr = _T("The audio device was removed. Make sure an audio device is available and retry playback.");
|
|
|
|
wxLogError(_T("AlsaPlayer: Error in thread: %s"), errstr.c_str());
|
2011-04-27 10:48:01 +02:00
|
|
|
}
|
|
|
|
else if (err == 0 && thread_result == 0)
|
|
|
|
{
|
|
|
|
// Successful exit
|
|
|
|
}
|
|
|
|
else if (err == EINVAL || err == ESRCH)
|
|
|
|
{
|
|
|
|
// Thread was already joined to or was never created, ignore this
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// EDEADLK but the playback thread shouldn't be trying to join us
|
2011-04-27 11:01:21 +02:00
|
|
|
wxLogError(_T("AlsaPlayer: Unexpected thread error, report this to the developers"));
|
2011-04-27 10:48:01 +02:00
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
void AlsaPlayer::Play(int64_t start, int64_t count)
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-26 22:53:19 +02:00
|
|
|
OpenStream();
|
|
|
|
|
|
|
|
{
|
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
ps.signal_start = true;
|
|
|
|
ps.signal_stop = true; // make sure to stop any ongoing playback first
|
|
|
|
ps.start_position = start;
|
|
|
|
ps.end_position = start + count;
|
|
|
|
pthread_cond_signal(&ps.cond);
|
2007-04-25 00:29:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (displayTimer && !displayTimer->IsRunning()) displayTimer->Start(15);
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-25 00:29:27 +02:00
|
|
|
void AlsaPlayer::Stop(bool timerToo)
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsPlaying()) return;
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
{
|
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
ps.signal_stop = true;
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("AlsaPlayer: stop stream, stop signal\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
pthread_cond_signal(&ps.cond);
|
|
|
|
}
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
if (timerToo && displayTimer) {
|
|
|
|
displayTimer->Stop();
|
|
|
|
}
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-27 10:48:01 +02:00
|
|
|
bool AlsaPlayer::IsAlive()
|
|
|
|
{
|
|
|
|
return ps.alive;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-25 00:29:27 +02:00
|
|
|
bool AlsaPlayer::IsPlaying()
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
return ps.playing; // should always be false if ps.alive is false
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-31 16:11:35 +02:00
|
|
|
void AlsaPlayer::SetEndPosition(int64_t pos)
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsPlaying()) return;
|
2011-04-26 22:53:19 +02:00
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
ps.end_position = pos;
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-31 16:11:35 +02:00
|
|
|
void AlsaPlayer::SetCurrentPosition(int64_t pos)
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsPlaying()) return;
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
|
|
|
|
ps.start_position = pos;
|
|
|
|
ps.signal_start = true;
|
|
|
|
ps.signal_stop = true;
|
2011-04-27 10:48:01 +02:00
|
|
|
//printf("AlsaPlayer: set position, stop+start signal\n");
|
2011-04-26 22:53:19 +02:00
|
|
|
pthread_cond_signal(&ps.cond);
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-31 16:11:35 +02:00
|
|
|
int64_t AlsaPlayer::GetStartPosition()
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsPlaying()) return 0;
|
2011-04-26 22:53:19 +02:00
|
|
|
return ps.start_position;
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-31 16:11:35 +02:00
|
|
|
int64_t AlsaPlayer::GetEndPosition()
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsPlaying()) return 0;
|
2011-04-26 22:53:19 +02:00
|
|
|
return ps.end_position;
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-31 16:11:35 +02:00
|
|
|
int64_t AlsaPlayer::GetCurrentPosition()
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsPlaying()) return 0;
|
2011-04-26 22:53:19 +02:00
|
|
|
|
|
|
|
int64_t lastpos;
|
|
|
|
timespec lasttime;
|
|
|
|
int64_t samplerate;
|
|
|
|
|
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
// Try to do this without synchronisation
|
|
|
|
// Might give unreliable results but shouldn't be catastrophic
|
2011-04-26 22:53:19 +02:00
|
|
|
//PthreadMutexLocker ml(ps.mutex);
|
|
|
|
lastpos = ps.last_position;
|
|
|
|
lasttime = ps.last_position_time;
|
|
|
|
samplerate = ps.provider->GetSampleRate();
|
|
|
|
}
|
|
|
|
|
|
|
|
timespec now;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &now);
|
|
|
|
|
|
|
|
const double NANO = 1000000000; // nano- is 10^-9
|
|
|
|
|
|
|
|
double now_sec = now.tv_sec + now.tv_nsec/NANO;
|
|
|
|
double last_sec = lasttime.tv_sec + lasttime.tv_nsec/NANO;
|
|
|
|
double diff_sec = now_sec - last_sec;
|
|
|
|
|
|
|
|
int64_t pos = lastpos + (int64_t)(diff_sec * samplerate);
|
|
|
|
//printf("AlsaPlayer: current position = %lld\n", pos);
|
|
|
|
|
|
|
|
return pos;
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
void AlsaPlayer::SetVolume(double vol)
|
2007-04-22 23:59:35 +02:00
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsAlive()) return;
|
2011-04-26 22:53:19 +02:00
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
ps.volume = vol;
|
|
|
|
ps.signal_volume = true;
|
|
|
|
pthread_cond_signal(&ps.cond);
|
|
|
|
}
|
2007-04-25 00:29:27 +02:00
|
|
|
|
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
double AlsaPlayer::GetVolume()
|
|
|
|
{
|
2011-04-27 10:48:01 +02:00
|
|
|
if (!IsAlive()) return 1.0;
|
2011-04-26 22:53:19 +02:00
|
|
|
PthreadMutexLocker ml(ps.mutex);
|
|
|
|
return ps.volume;
|
|
|
|
}
|
2007-04-26 22:11:48 +02:00
|
|
|
|
2007-04-25 00:29:27 +02:00
|
|
|
|
2011-04-26 22:53:19 +02:00
|
|
|
// The factory method
|
|
|
|
AudioPlayer * AlsaPlayerFactory::CreatePlayer()
|
|
|
|
{
|
|
|
|
return new AlsaPlayer();
|
2007-04-22 23:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-01-21 21:57:20 +01:00
|
|
|
#endif // WITH_ALSA
|
2011-04-26 22:53:19 +02:00
|
|
|
|