Fixed Alsa player, now mostly works. (Incorrect position reporting, strange samplerate glitch at start of playback.) And more fixes to build system.
Originally committed to SVN as r1128.
This commit is contained in:
parent
a278e4e4a3
commit
14582ad00a
2 changed files with 213 additions and 265 deletions
|
@ -1,5 +1,4 @@
|
|||
EXTRA_DIST = res.rc $(srcdir)/*.h \
|
||||
subtitles_provider_libass.cpp
|
||||
EXTRA_DIST = res.rc $(srcdir)/*.h
|
||||
SUFFIXES = .c .cpp .rc
|
||||
|
||||
SUBDIRS = bitmaps posix
|
||||
|
@ -12,7 +11,8 @@ REVISION := $(if $(SVNREV),-DBUILD_SVN_REVISION=$(SVNREV)) $(if $(DARCSREV),-DBU
|
|||
BUILDINFO := -DBUILD_CREDIT="\"$(shell whoami)\"" $(REVISION)
|
||||
|
||||
AM_CPPFLAGST = -DAEGISUB -Iposix -include posix/defines.h $(BUILDINFO)
|
||||
LDADD = posix/libposix.a ../lua51/src/liblua.a ../FexTrackerSource/libfex.a ../csri/lib/.libs/libcsri.a
|
||||
aegisub_LDADD = posix/libposix.a ../lua51/src/liblua.a ../FexTrackerSource/libfex.a ../csri/lib/.libs/libcsri.a
|
||||
aegisub_LDFLAGS =
|
||||
|
||||
if USE_LIBASS
|
||||
LIBASS=subtitles_provider_libass.cpp
|
||||
|
@ -23,15 +23,15 @@ endif
|
|||
AUDIO_PLAYER=audio_player.cpp
|
||||
if HAVE_PORTAUDIO
|
||||
AUDIO_PLAYER += audio_player_portaudio.cpp
|
||||
LDADD += -lportaudio
|
||||
aegisub_LDFLAGS += -lportaudio
|
||||
endif
|
||||
if HAVE_ALSA
|
||||
AUDIO_PLAYER += audio_player_alsa.cpp
|
||||
LDADD += -lasound
|
||||
aegisub_LDFLAGS += -lasound
|
||||
endif
|
||||
if HAVE_PULSEAUDIO
|
||||
AUDIO_PLAYER += audio_player_pulse.cpp
|
||||
LDADD += -lpulse
|
||||
aegisub_LDFLAGS += -lpulse
|
||||
endif
|
||||
|
||||
if HAVE_RUBY
|
||||
|
@ -54,6 +54,19 @@ else
|
|||
FFMPEG=
|
||||
endif
|
||||
|
||||
|
||||
## These aren't built, but are listed here so 'make dist' can always find all the sources
|
||||
EXTRA_aegisub_SOURCES = \
|
||||
subtitles_provider_libass.cpp \
|
||||
spellchecker_hunspell.cpp \
|
||||
audio_player_portaudio.cpp \
|
||||
audio_player_alsa.cpp \
|
||||
audio_player_pulse.cpp \
|
||||
audio_player_dsound.cpp \
|
||||
audio_provider_lavc.cpp \
|
||||
lavc_file.cpp \
|
||||
video_provider_lavc.cpp
|
||||
|
||||
aegisub_SOURCES = \
|
||||
$(AUDIO_PLAYER) \
|
||||
$(AUTO4RUBY) \
|
||||
|
|
|
@ -47,27 +47,23 @@
|
|||
#include "options.h"
|
||||
|
||||
|
||||
//////////////
|
||||
// Prototypes
|
||||
class AlsaPlayer;
|
||||
|
||||
|
||||
//////////
|
||||
// Thread
|
||||
class AlsaPlayerThread : public wxThread {
|
||||
///////////////
|
||||
// Alsa player
|
||||
class AlsaPlayer : public AudioPlayer {
|
||||
private:
|
||||
wxMutex play_mutex; // held while playing
|
||||
wxSemaphore play_notify; // posted when a playback operation is set up
|
||||
wxSemaphore stop_notify; // when set, audio playback should stop asap
|
||||
wxSemaphore shutdown_notify; // when set, thread should shutdown asap
|
||||
wxMutex parameter_mutex;
|
||||
bool open;
|
||||
volatile bool playing;
|
||||
volatile float volume;
|
||||
|
||||
AudioProvider *provider; // provides sample data!
|
||||
volatile unsigned long start_frame; // first frame of playback
|
||||
volatile unsigned long cur_frame; // last written frame + 1
|
||||
volatile unsigned long end_frame; // last frame to play
|
||||
unsigned long bpf; // bytes per frame
|
||||
|
||||
AudioProvider *provider;
|
||||
snd_pcm_t *pcm_handle; // device handle
|
||||
snd_pcm_stream_t stream; // stream direction
|
||||
snd_pcm_hw_params_t *hwparams; // hardware info
|
||||
char *pcm_name; // device name
|
||||
snd_async_handler_t *pcm_callback;
|
||||
|
||||
snd_pcm_format_t sample_format;
|
||||
unsigned int rate; // sample rate of audio
|
||||
|
@ -75,40 +71,10 @@ private:
|
|||
int periods; // number of bytes in a frame
|
||||
snd_pcm_uframes_t bufsize; // size of buffer in frames
|
||||
|
||||
volatile double volume; // volume to get audio at
|
||||
volatile unsigned long start_frame; // first frame of playback
|
||||
volatile unsigned long cur_frame; // last written frame + 1
|
||||
volatile unsigned long end_frame; // last frame to play
|
||||
|
||||
void SetUpHardware();
|
||||
void PlaybackLoop();
|
||||
void SetUpAsync();
|
||||
|
||||
public:
|
||||
AlsaPlayerThread(AudioProvider *_provider);
|
||||
~AlsaPlayerThread();
|
||||
wxThread::ExitCode Entry();
|
||||
|
||||
// The following methods are all thread safe
|
||||
void Play(unsigned long _start_frame, unsigned long _num_frames); // Notify thread to stop any playback and instead play specified range
|
||||
void SetEndPosition(unsigned long _end_frame); // Notify thread to use new end position
|
||||
void Stop(); // Notify thread to stop audio playback
|
||||
void StopWait(); // Notify thread to stop, and wait for it to do it
|
||||
void Shutdown(); // Notify the thread to stop playback and die
|
||||
void SetVolume(double new_volume); // Set volume
|
||||
unsigned long GetStartPosition(); // Get first played back frame number
|
||||
unsigned long GetEndPosition(); // Get last frame number to be played back
|
||||
unsigned long GetCurrentPosition(); // Get currently played back frame number
|
||||
bool IsPlaying();
|
||||
};
|
||||
|
||||
|
||||
///////////////
|
||||
// Alsa player
|
||||
class AlsaPlayer : public AudioPlayer {
|
||||
private:
|
||||
float volume;
|
||||
|
||||
AlsaPlayerThread *thread;
|
||||
static void async_write_handler(snd_async_handler_t *pcm_callback);
|
||||
|
||||
public:
|
||||
AlsaPlayer();
|
||||
|
@ -145,143 +111,60 @@ public:
|
|||
|
||||
///////////////
|
||||
// Constructor
|
||||
AlsaPlayer::AlsaPlayer() {
|
||||
AlsaPlayer::AlsaPlayer()
|
||||
{
|
||||
volume = 1.0f;
|
||||
thread = NULL;
|
||||
open = false;
|
||||
playing = false;
|
||||
start_frame = cur_frame = end_frame = bpf = 0;
|
||||
provider = 0;
|
||||
}
|
||||
|
||||
|
||||
//////////////
|
||||
// Destructor
|
||||
AlsaPlayer::~AlsaPlayer() {
|
||||
AlsaPlayer::~AlsaPlayer()
|
||||
{
|
||||
CloseStream();
|
||||
}
|
||||
|
||||
|
||||
///////////////
|
||||
// Open stream
|
||||
void AlsaPlayer::OpenStream() {
|
||||
void AlsaPlayer::OpenStream()
|
||||
{
|
||||
CloseStream();
|
||||
|
||||
// Get provider
|
||||
AudioProvider *provider = GetProvider();
|
||||
provider = GetProvider();
|
||||
bpf = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
|
||||
thread = new AlsaPlayerThread(provider);
|
||||
}
|
||||
|
||||
|
||||
////////////////
|
||||
// Close stream
|
||||
void AlsaPlayer::CloseStream() {
|
||||
if (!thread) return;
|
||||
|
||||
thread->Shutdown();
|
||||
thread->Wait();
|
||||
thread = 0;
|
||||
}
|
||||
|
||||
|
||||
////////
|
||||
// Play
|
||||
void AlsaPlayer::Play(__int64 start,__int64 count) {
|
||||
// Make sure that it's stopped
|
||||
thread->Stop();
|
||||
|
||||
thread->Play(start, count);
|
||||
}
|
||||
|
||||
|
||||
////////
|
||||
// Stop
|
||||
void AlsaPlayer::Stop(bool timerToo) {
|
||||
if (thread) thread->Stop();
|
||||
|
||||
if (timerToo && displayTimer) {
|
||||
displayTimer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool AlsaPlayer::IsPlaying()
|
||||
{
|
||||
return thread && thread->IsPlaying();
|
||||
}
|
||||
|
||||
|
||||
///////////
|
||||
// Set end
|
||||
void AlsaPlayer::SetEndPosition(__int64 pos) {
|
||||
if (thread) thread->SetEndPosition(pos);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////
|
||||
// Set current position
|
||||
void AlsaPlayer::SetCurrentPosition(__int64 pos) {
|
||||
assert(false); // not supported (yet?)
|
||||
// I believe this isn't used anywhere. I hope not.
|
||||
}
|
||||
|
||||
|
||||
__int64 AlsaPlayer::GetStartPosition()
|
||||
{
|
||||
if (thread) return thread->GetStartPosition();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
__int64 AlsaPlayer::GetEndPosition()
|
||||
{
|
||||
if (thread) return thread->GetEndPosition();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////
|
||||
// Get current position
|
||||
__int64 AlsaPlayer::GetCurrentPosition() {
|
||||
if (thread) return thread->GetCurrentPosition();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////
|
||||
// Thread constructor
|
||||
AlsaPlayerThread::AlsaPlayerThread(AudioProvider *_provider)
|
||||
: wxThread(wxTHREAD_JOINABLE)
|
||||
, play_notify(0, 1)
|
||||
, stop_notify(0, 1)
|
||||
, shutdown_notify(0, 1)
|
||||
, provider(_provider)
|
||||
{
|
||||
// We want playback
|
||||
stream = SND_PCM_STREAM_PLAYBACK;
|
||||
// Use default device and automatic sample type conversion
|
||||
// And get a device name
|
||||
wxString device = Options.AsText(_T("Audio Alsa Device"));
|
||||
pcm_name = strdup(device.mb_str(wxConvUTF8));
|
||||
|
||||
// Allocate params structure
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
|
||||
// Open device for blocking access
|
||||
if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
|
||||
throw _T("Error opening default PCM device");
|
||||
if (snd_pcm_open(&pcm_handle, device.mb_str(wxConvUTF8), stream, 0) < 0) { // supposedly we don't want SND_PCM_ASYNC even for async playback
|
||||
throw _T("Error opening specified PCM device");
|
||||
}
|
||||
|
||||
SetUpHardware();
|
||||
|
||||
// Register async handler
|
||||
SetUpAsync();
|
||||
|
||||
// Now ready
|
||||
open = true;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////
|
||||
// Thread destructor
|
||||
AlsaPlayerThread::~AlsaPlayerThread() {
|
||||
free(pcm_name);
|
||||
}
|
||||
|
||||
|
||||
void AlsaPlayerThread::SetUpHardware()
|
||||
void AlsaPlayer::SetUpHardware()
|
||||
{
|
||||
// Allocate params structure
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_hw_params_malloc(&hwparams);
|
||||
|
||||
// Get hardware params
|
||||
if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
|
||||
throw _T("Error setting up default PCM device");
|
||||
|
@ -293,7 +176,16 @@ void AlsaPlayerThread::SetUpHardware()
|
|||
}
|
||||
|
||||
// Set sample format
|
||||
sample_format = SND_PCM_FORMAT_S16_LE; // TODO: support other formats
|
||||
switch (provider->GetBytesPerSample()) {
|
||||
case 1:
|
||||
sample_format = SND_PCM_FORMAT_S8;
|
||||
break;
|
||||
case 2:
|
||||
sample_format = SND_PCM_FORMAT_S16_LE;
|
||||
break;
|
||||
default:
|
||||
throw _T("Can only handle 8 and 16 bit sound");
|
||||
}
|
||||
if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, sample_format) < 0) {
|
||||
throw _T("Could not set sample format");
|
||||
}
|
||||
|
@ -313,8 +205,9 @@ void AlsaPlayerThread::SetUpHardware()
|
|||
throw _T("Could not set number of channels");
|
||||
}
|
||||
|
||||
// Set periods (size in bytes of one frame?)
|
||||
periods = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
// Set periods (number of bytes ideally written at a time)
|
||||
// Somewhat arbitrary for now (256 frames)
|
||||
periods = provider->GetChannels() * provider->GetBytesPerSample() * 256;
|
||||
if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) {
|
||||
throw _T("Could not set periods");
|
||||
}
|
||||
|
@ -333,145 +226,187 @@ void AlsaPlayerThread::SetUpHardware()
|
|||
if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
|
||||
throw _T("Failed applying sound hardware settings");
|
||||
}
|
||||
|
||||
// And free memory again
|
||||
snd_pcm_hw_params_free(hwparams);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////
|
||||
// Thread entry point
|
||||
wxThread::ExitCode AlsaPlayerThread::Entry()
|
||||
void AlsaPlayer::SetUpAsync()
|
||||
{
|
||||
// Loop as long as we aren't told to shutdown
|
||||
while (shutdown_notify.TryWait() == wxSEMA_BUSY) {
|
||||
// Wait for a playback operation
|
||||
while (play_notify.WaitTimeout(100) == wxSEMA_NO_ERROR) {
|
||||
// So playback was posted... now play something
|
||||
play_mutex.Lock();
|
||||
PlaybackLoop();
|
||||
play_mutex.Unlock();
|
||||
}
|
||||
// Prepare software params struct
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_sw_params_malloc (&sw_params);
|
||||
|
||||
// Get current parameters
|
||||
if (snd_pcm_sw_params_current(pcm_handle, sw_params) < 0) {
|
||||
throw _T("Couldn't get current SW params");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AlsaPlayerThread::PlaybackLoop()
|
||||
{
|
||||
unsigned long datalen = rate/2;
|
||||
void *data = malloc(datalen * periods);
|
||||
unsigned long cur_pos = 0;
|
||||
|
||||
while (true) {
|
||||
// Get/set parameters
|
||||
parameter_mutex.Lock();
|
||||
double vol = volume;
|
||||
cur_frame = cur_pos;
|
||||
unsigned long end_pos = end_frame;
|
||||
parameter_mutex.Unlock();
|
||||
|
||||
// Check for stop
|
||||
if (stop_notify.TryWait() != wxSEMA_BUSY) {
|
||||
snd_pcm_drop(pcm_handle);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for end of stream
|
||||
if (cur_pos >= end_pos) {
|
||||
snd_pcm_drain(pcm_handle);
|
||||
break;
|
||||
}
|
||||
|
||||
// Write some frames
|
||||
provider->GetAudioWithVolume(data, cur_pos, datalen, vol);
|
||||
snd_pcm_sframes_t written = snd_pcm_writei(pcm_handle, data, datalen);
|
||||
cur_pos += written;
|
||||
// How full the buffer must be before playback begins
|
||||
if (snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, bufsize/2) < 0) {
|
||||
throw _T("Failed setting start threshold");
|
||||
}
|
||||
|
||||
free(data);
|
||||
// The the largest write guaranteed never to block
|
||||
if (snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, bufsize/4) < 0) {
|
||||
throw _T("Failed setting min available buffer");
|
||||
}
|
||||
|
||||
// Apply settings
|
||||
if (snd_pcm_sw_params(pcm_handle, sw_params) < 0) {
|
||||
throw _T("Failed applying SW params");
|
||||
}
|
||||
|
||||
// And free struct again
|
||||
snd_pcm_sw_params_free(sw_params);
|
||||
|
||||
// Attach async handler
|
||||
if (snd_async_add_pcm_handler(&pcm_callback, pcm_handle, async_write_handler, this) < 0) {
|
||||
throw _T("Failed attaching async handler");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AlsaPlayerThread::Play(unsigned long _start_frame, unsigned long _num_frames)
|
||||
////////////////
|
||||
// Close stream
|
||||
void AlsaPlayer::CloseStream()
|
||||
{
|
||||
StopWait();
|
||||
if (!open) return;
|
||||
|
||||
cur_frame = start_frame = _start_frame;
|
||||
end_frame = start_frame + _num_frames;
|
||||
|
||||
play_notify.Post();
|
||||
Stop();
|
||||
|
||||
// Remove async handler
|
||||
snd_async_del_handler(pcm_callback);
|
||||
|
||||
// Close device
|
||||
snd_pcm_close(pcm_handle);
|
||||
|
||||
// No longer working
|
||||
open = false;
|
||||
}
|
||||
|
||||
|
||||
void AlsaPlayerThread::SetEndPosition(unsigned long _end_frame)
|
||||
////////
|
||||
// Play
|
||||
void AlsaPlayer::Play(__int64 start,__int64 count)
|
||||
{
|
||||
parameter_mutex.Lock();
|
||||
end_frame = _end_frame;
|
||||
parameter_mutex.Unlock();
|
||||
if (playing) {
|
||||
// Quick reset
|
||||
playing = false;
|
||||
snd_pcm_drop(pcm_handle);
|
||||
}
|
||||
|
||||
// Set params
|
||||
start_frame = start;
|
||||
cur_frame = start;
|
||||
end_frame = start + count;
|
||||
playing = true;
|
||||
|
||||
// Prepare a bit
|
||||
snd_pcm_prepare (pcm_handle);
|
||||
async_write_handler(pcm_callback);
|
||||
|
||||
// And go!
|
||||
snd_pcm_start(pcm_handle);
|
||||
|
||||
// Update timer
|
||||
if (displayTimer && !displayTimer->IsRunning()) displayTimer->Start(15);
|
||||
}
|
||||
|
||||
|
||||
////////
|
||||
// Stop
|
||||
void AlsaPlayer::Stop(bool timerToo)
|
||||
{
|
||||
if (!open) return;
|
||||
if (!playing) return;
|
||||
|
||||
// Reset data
|
||||
playing = false;
|
||||
start_frame = 0;
|
||||
cur_frame = 0;
|
||||
end_frame = 0;
|
||||
|
||||
// Then drop the playback
|
||||
snd_pcm_drop(pcm_handle);
|
||||
|
||||
if (timerToo && displayTimer) {
|
||||
displayTimer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool AlsaPlayer::IsPlaying()
|
||||
{
|
||||
return playing;
|
||||
}
|
||||
|
||||
|
||||
///////////
|
||||
// Set end
|
||||
void AlsaPlayer::SetEndPosition(__int64 pos)
|
||||
{
|
||||
end_frame = pos;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////
|
||||
// Stop playback thread
|
||||
void AlsaPlayerThread::Stop()
|
||||
// Set current position
|
||||
void AlsaPlayer::SetCurrentPosition(__int64 pos)
|
||||
{
|
||||
stop_notify.Post();
|
||||
cur_frame = pos;
|
||||
}
|
||||
|
||||
|
||||
void AlsaPlayerThread::StopWait()
|
||||
__int64 AlsaPlayer::GetStartPosition()
|
||||
{
|
||||
stop_notify.Post();
|
||||
// Now wait for the play mutex to have been freed
|
||||
play_mutex.Lock();
|
||||
play_mutex.Unlock();
|
||||
}
|
||||
|
||||
|
||||
void AlsaPlayerThread::Shutdown()
|
||||
{
|
||||
Stop();
|
||||
shutdown_notify.Post();
|
||||
}
|
||||
|
||||
|
||||
void AlsaPlayerThread::SetVolume(double new_volume)
|
||||
{
|
||||
parameter_mutex.Lock();
|
||||
volume = new_volume;
|
||||
parameter_mutex.Unlock();
|
||||
}
|
||||
|
||||
|
||||
unsigned long AlsaPlayerThread::GetStartPosition()
|
||||
{
|
||||
wxMutexLocker lock(parameter_mutex);
|
||||
return start_frame;
|
||||
}
|
||||
|
||||
|
||||
unsigned long AlsaPlayerThread::GetEndPosition()
|
||||
__int64 AlsaPlayer::GetEndPosition()
|
||||
{
|
||||
wxMutexLocker lock(parameter_mutex);
|
||||
return end_frame;
|
||||
}
|
||||
|
||||
|
||||
unsigned long AlsaPlayerThread::GetCurrentPosition()
|
||||
////////////////////////
|
||||
// Get current position
|
||||
__int64 AlsaPlayer::GetCurrentPosition()
|
||||
{
|
||||
// Step 1: Get last written frame number
|
||||
parameter_mutex.Lock();
|
||||
unsigned long pos = cur_frame;
|
||||
parameter_mutex.Unlock();
|
||||
|
||||
// Step 2: ???
|
||||
|
||||
// Step 3: Profit!
|
||||
return pos;
|
||||
return cur_frame; // FIXME
|
||||
}
|
||||
|
||||
|
||||
bool AlsaPlayerThread::IsPlaying()
|
||||
void AlsaPlayer::async_write_handler(snd_async_handler_t *pcm_callback)
|
||||
{
|
||||
return play_mutex.TryLock() == wxMUTEX_BUSY;
|
||||
// TODO: check for broken pipes in here and restore as needed
|
||||
AlsaPlayer *player = (AlsaPlayer*)snd_async_handler_get_callback_private(pcm_callback);
|
||||
|
||||
if (player->cur_frame >= player->end_frame + player->rate) {
|
||||
// More than a second past end of stream
|
||||
snd_pcm_drain(player->pcm_handle);
|
||||
player->playing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t frames = snd_pcm_avail_update(player->pcm_handle);
|
||||
|
||||
if (player->cur_frame >= player->end_frame) {
|
||||
// Past end of stream, add some silence
|
||||
void *buf = calloc(frames, player->bpf);
|
||||
snd_pcm_writei(player->pcm_handle, buf, frames);
|
||||
free(buf);
|
||||
player->cur_frame += frames;
|
||||
return;
|
||||
}
|
||||
|
||||
void *buf = malloc(frames * player->bpf);
|
||||
player->provider->GetAudioWithVolume(buf, player->cur_frame, frames, player->volume);
|
||||
snd_pcm_writei(player->pcm_handle, buf, frames);
|
||||
free(buf);
|
||||
player->cur_frame += frames;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue