mirror of https://github.com/odrling/Aegisub
PulseAudio player seems to kinda work, it just doesn't report playback position yet
Originally committed to SVN as r1120.
This commit is contained in:
parent
760cb49475
commit
15a5e39fff
|
@ -37,12 +37,12 @@
|
||||||
///////////
|
///////////
|
||||||
// Headers
|
// Headers
|
||||||
#include <wx/wxprec.h>
|
#include <wx/wxprec.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "audio_player.h"
|
#include "audio_player.h"
|
||||||
#include "audio_provider.h"
|
#include "audio_provider.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include <pulse/pulseaudio.h>
|
#include <pulse/pulseaudio.h>
|
||||||
#include <pulse/simple.h>
|
|
||||||
|
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
|
@ -50,54 +50,47 @@
|
||||||
class PulseAudioPlayer;
|
class PulseAudioPlayer;
|
||||||
|
|
||||||
|
|
||||||
//////////
|
|
||||||
// Thread
|
|
||||||
class PulseAudioPlayerThread : public wxThread {
|
|
||||||
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;
|
|
||||||
|
|
||||||
AudioProvider *provider; // provides sample data!
|
|
||||||
|
|
||||||
pa_simple *s; // stream handle
|
|
||||||
int paerror;
|
|
||||||
|
|
||||||
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 PlaybackLoop();
|
|
||||||
|
|
||||||
public:
|
|
||||||
PulseAudioPlayerThread(AudioProvider *_provider);
|
|
||||||
~PulseAudioPlayerThread();
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
// Pulse Audio player
|
// Pulse Audio player
|
||||||
class PulseAudioPlayer : public AudioPlayer {
|
class PulseAudioPlayer : public AudioPlayer {
|
||||||
private:
|
private:
|
||||||
float volume;
|
float volume;
|
||||||
|
bool open;
|
||||||
|
bool is_playing;
|
||||||
|
|
||||||
PulseAudioPlayerThread *thread;
|
// Audio data info
|
||||||
|
unsigned long start_frame;
|
||||||
|
unsigned long cur_frame;
|
||||||
|
unsigned long end_frame;
|
||||||
|
unsigned long bpf; // bytes per frame
|
||||||
|
|
||||||
|
// Used for synchronising with async events
|
||||||
|
wxSemaphore context_notify;
|
||||||
|
wxSemaphore context_success;
|
||||||
|
volatile int context_success_val;
|
||||||
|
wxSemaphore stream_notify;
|
||||||
|
wxSemaphore stream_success;
|
||||||
|
volatile int stream_success_val;
|
||||||
|
|
||||||
|
// PulseAudio data
|
||||||
|
pa_threaded_mainloop *mainloop; // pulseaudio mainloop handle
|
||||||
|
pa_context *context; // connection context
|
||||||
|
volatile pa_context_state_t cstate;
|
||||||
|
pa_stream *stream;
|
||||||
|
volatile pa_stream_state_t sstate;
|
||||||
|
int paerror;
|
||||||
|
|
||||||
|
// Called by PA to notify about contetxt operation completion
|
||||||
|
static void pa_context_success(pa_context *c, int success, PulseAudioPlayer *thread);
|
||||||
|
// Called by PA to notify about other context-related stuff
|
||||||
|
static void pa_context_notify(pa_context *c, PulseAudioPlayer *thread);
|
||||||
|
// Called by PA when a stream operation completes
|
||||||
|
static void pa_stream_success(pa_stream *p, int success, PulseAudioPlayer *thread);
|
||||||
|
// Called by PA to request more data written to stream
|
||||||
|
static void pa_stream_write(pa_stream *p, size_t length, PulseAudioPlayer *thread);
|
||||||
|
// Called by PA to notify about other stream-related stuff
|
||||||
|
static void pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PulseAudioPlayer();
|
PulseAudioPlayer();
|
||||||
|
@ -134,295 +127,308 @@ public:
|
||||||
|
|
||||||
///////////////
|
///////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
PulseAudioPlayer::PulseAudioPlayer() {
|
PulseAudioPlayer::PulseAudioPlayer()
|
||||||
|
{
|
||||||
volume = 1.0f;
|
volume = 1.0f;
|
||||||
thread = NULL;
|
paerror = 0;
|
||||||
|
open = false;
|
||||||
|
is_playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
// Destructor
|
// Destructor
|
||||||
PulseAudioPlayer::~PulseAudioPlayer() {
|
PulseAudioPlayer::~PulseAudioPlayer()
|
||||||
CloseStream();
|
{
|
||||||
|
if (open) CloseStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////
|
///////////////
|
||||||
// Open stream
|
// Open stream
|
||||||
void PulseAudioPlayer::OpenStream() {
|
void PulseAudioPlayer::OpenStream()
|
||||||
CloseStream();
|
{
|
||||||
|
printf("Opening PulseAudio stream\n");
|
||||||
|
if (open) CloseStream();
|
||||||
|
|
||||||
// Get provider
|
// Get provider
|
||||||
AudioProvider *provider = GetProvider();
|
AudioProvider *provider = GetProvider();
|
||||||
|
|
||||||
try {
|
// Initialise a mainloop
|
||||||
thread = new PulseAudioPlayerThread(provider);
|
printf("Initialising threaded main loop\n");
|
||||||
thread->Create();
|
mainloop = pa_threaded_mainloop_new();
|
||||||
thread->Run();
|
if (!mainloop) {
|
||||||
|
throw _T("Failed to initialise PulseAudio threaded mainloop object");
|
||||||
}
|
}
|
||||||
catch (const char *e) {
|
printf("Starting main loop\n");
|
||||||
wxLogError(_T("Failed to initialise PulseAudio: %s"), wxString(e, wxConvLocal).c_str());
|
pa_threaded_mainloop_start(mainloop);
|
||||||
|
|
||||||
|
// Create context
|
||||||
|
printf("Creating context\n");
|
||||||
|
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "Aegisub");
|
||||||
|
if (!context) {
|
||||||
|
pa_threaded_mainloop_free(mainloop);
|
||||||
|
throw _T("Failed to create PulseAudio context");
|
||||||
}
|
}
|
||||||
|
pa_context_set_state_callback(context, (pa_context_notify_cb_t)pa_context_notify, this);
|
||||||
|
|
||||||
|
// Connect the context
|
||||||
|
printf("Connecting context\n");
|
||||||
|
pa_context_connect(context, NULL, 0, NULL);
|
||||||
|
// Wait for connection
|
||||||
|
while (true) {
|
||||||
|
context_notify.Wait();
|
||||||
|
if (cstate == PA_CONTEXT_READY) {
|
||||||
|
break;
|
||||||
|
} else if (cstate == PA_CONTEXT_FAILED) {
|
||||||
|
// eww
|
||||||
|
paerror = pa_context_errno(context);
|
||||||
|
pa_context_unref(context);
|
||||||
|
pa_threaded_mainloop_stop(mainloop);
|
||||||
|
pa_threaded_mainloop_free(mainloop);
|
||||||
|
wxString s(pa_strerror(paerror), wxConvUTF8);
|
||||||
|
throw s.c_str();
|
||||||
|
}
|
||||||
|
// otherwise loop once more
|
||||||
|
}
|
||||||
|
printf("Context connected\n");
|
||||||
|
|
||||||
|
// Set up stream
|
||||||
|
bpf = provider->GetChannels() * provider->GetBytesPerSample();
|
||||||
|
pa_sample_spec ss;
|
||||||
|
ss.format = PA_SAMPLE_S16LE; // FIXME
|
||||||
|
ss.rate = provider->GetSampleRate();
|
||||||
|
ss.channels = provider->GetChannels();
|
||||||
|
pa_channel_map map;
|
||||||
|
pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT);
|
||||||
|
printf("Creating stream\n");
|
||||||
|
stream = pa_stream_new(context, "Sound", &ss, &map);
|
||||||
|
if (!stream) {
|
||||||
|
// argh!
|
||||||
|
pa_context_disconnect(context);
|
||||||
|
pa_context_unref(context);
|
||||||
|
pa_threaded_mainloop_stop(mainloop);
|
||||||
|
pa_threaded_mainloop_free(mainloop);
|
||||||
|
throw _T("PulseAudio could not create stream");
|
||||||
|
}
|
||||||
|
pa_stream_set_state_callback(stream, (pa_stream_notify_cb_t)pa_stream_notify, this);
|
||||||
|
pa_stream_set_write_callback(stream, (pa_stream_request_cb_t)pa_stream_write, this);
|
||||||
|
|
||||||
|
// Connext stream
|
||||||
|
printf("Connecting playback stream\n");
|
||||||
|
paerror = pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_NOT_MONOTONOUS|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
|
||||||
|
if (paerror) {
|
||||||
|
printf("PulseAudio reported error: %s\n", pa_strerror(paerror));
|
||||||
|
wxString s(pa_strerror(paerror), wxConvUTF8);
|
||||||
|
throw s.c_str();
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
stream_notify.Wait();
|
||||||
|
if (sstate == PA_STREAM_READY) {
|
||||||
|
break;
|
||||||
|
} else if (sstate == PA_STREAM_FAILED) {
|
||||||
|
printf("Stream connection failed for some reason\n");
|
||||||
|
throw _T("Something went wrong connecting the stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("Connected playback stream, now playing\n\n");
|
||||||
|
|
||||||
|
// Hopefully this marks success
|
||||||
|
printf("Finished opening PulseAudio\n\n");
|
||||||
|
open = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// Close stream
|
// Close stream
|
||||||
void PulseAudioPlayer::CloseStream() {
|
void PulseAudioPlayer::CloseStream()
|
||||||
if (!thread) return;
|
{
|
||||||
|
if (!open) return;
|
||||||
|
printf("Closing PuseAudio\n");
|
||||||
|
|
||||||
thread->Shutdown();
|
if (is_playing) Stop();
|
||||||
thread->Wait();
|
|
||||||
thread = 0;
|
// Hope for the best and just do things as quickly as possible
|
||||||
|
pa_stream_disconnect(stream);
|
||||||
|
pa_stream_unref(stream);
|
||||||
|
pa_context_disconnect(context);
|
||||||
|
pa_context_unref(context);
|
||||||
|
pa_threaded_mainloop_stop(mainloop);
|
||||||
|
pa_threaded_mainloop_free(mainloop);
|
||||||
|
|
||||||
|
printf("Closed PulseAudio\n");
|
||||||
|
open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////
|
////////
|
||||||
// Play
|
// Play
|
||||||
void PulseAudioPlayer::Play(__int64 start,__int64 count) {
|
void PulseAudioPlayer::Play(__int64 start,__int64 count)
|
||||||
// Make sure that it's stopped
|
{
|
||||||
thread->Stop();
|
printf("Starting PulseAudio playback\n");
|
||||||
|
if (!open) OpenStream();
|
||||||
|
if (is_playing) Stop();
|
||||||
|
|
||||||
thread->Play(start, count);
|
start_frame = start;
|
||||||
|
cur_frame = start;
|
||||||
|
end_frame = start + count;
|
||||||
|
printf("start=%lu end=%lu\n", start_frame, end_frame);
|
||||||
|
|
||||||
|
is_playing = true;
|
||||||
|
|
||||||
|
PulseAudioPlayer::pa_stream_write(stream, pa_stream_writable_size(stream), this);
|
||||||
|
|
||||||
|
pa_operation *op = pa_stream_trigger(stream, (pa_stream_success_cb_t)pa_stream_success, this);
|
||||||
|
stream_success.Wait();
|
||||||
|
pa_operation_unref(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////
|
////////
|
||||||
// Stop
|
// Stop
|
||||||
void PulseAudioPlayer::Stop(bool timerToo) {
|
void PulseAudioPlayer::Stop(bool timerToo)
|
||||||
if (thread) thread->Stop();
|
{
|
||||||
|
if (!is_playing) return;
|
||||||
|
printf("Stopping PulseAudio\n");
|
||||||
|
|
||||||
if (timerToo && displayTimer) {
|
is_playing = false;
|
||||||
displayTimer->Stop();
|
|
||||||
|
pa_operation *op = pa_stream_cork(stream, 0, (pa_stream_success_cb_t)pa_stream_success, this);
|
||||||
|
stream_success.Wait();
|
||||||
|
pa_operation_unref(op);
|
||||||
|
|
||||||
|
start_frame = 0;
|
||||||
|
cur_frame = 0;
|
||||||
|
end_frame = 0;
|
||||||
|
|
||||||
|
// Flush the stream of data
|
||||||
|
printf("Draining stream\n");
|
||||||
|
op = pa_stream_flush(stream, (pa_stream_success_cb_t)pa_stream_success, this);
|
||||||
|
stream_success.Wait();
|
||||||
|
pa_operation_unref(op);
|
||||||
|
|
||||||
|
// Then disconnect it
|
||||||
|
/*printf("Disconnecting stream\n");
|
||||||
|
paerror = pa_stream_disconnect(stream);
|
||||||
|
if (paerror) {
|
||||||
|
printf("PulseAudio reported error: %s\n", pa_strerror(paerror));
|
||||||
|
wxString s(pa_strerror(paerror), wxConvUTF8);
|
||||||
|
throw s.c_str();
|
||||||
}
|
}
|
||||||
|
while (true) {
|
||||||
|
stream_notify.Wait();
|
||||||
|
if (sstate == PA_STREAM_TERMINATED) {
|
||||||
|
break;
|
||||||
|
} else if (sstate == PA_STREAM_FAILED) {
|
||||||
|
printf("Stream stopping failed\n");
|
||||||
|
throw _T("Something went wrong disconnecting the stream?!");
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// And unref it
|
||||||
|
printf("Stopped stream\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PulseAudioPlayer::IsPlaying()
|
bool PulseAudioPlayer::IsPlaying()
|
||||||
{
|
{
|
||||||
return thread && thread->IsPlaying();
|
return is_playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////
|
///////////
|
||||||
// Set end
|
// Set end
|
||||||
void PulseAudioPlayer::SetEndPosition(__int64 pos) {
|
void PulseAudioPlayer::SetEndPosition(__int64 pos)
|
||||||
if (thread) thread->SetEndPosition(pos);
|
{
|
||||||
|
end_frame = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
// Set current position
|
// Set current position
|
||||||
void PulseAudioPlayer::SetCurrentPosition(__int64 pos) {
|
void PulseAudioPlayer::SetCurrentPosition(__int64 pos)
|
||||||
assert(false); // not supported (yet?)
|
{
|
||||||
// I believe this isn't used anywhere. I hope not.
|
cur_frame = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
__int64 PulseAudioPlayer::GetStartPosition()
|
__int64 PulseAudioPlayer::GetStartPosition()
|
||||||
{
|
{
|
||||||
if (thread) return thread->GetStartPosition();
|
return start_frame;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
__int64 PulseAudioPlayer::GetEndPosition()
|
__int64 PulseAudioPlayer::GetEndPosition()
|
||||||
{
|
{
|
||||||
if (thread) return thread->GetEndPosition();
|
return end_frame;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
// Get current position
|
// Get current position
|
||||||
__int64 PulseAudioPlayer::GetCurrentPosition() {
|
__int64 PulseAudioPlayer::GetCurrentPosition()
|
||||||
if (thread) return thread->GetCurrentPosition();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////
|
|
||||||
// Thread constructor
|
|
||||||
PulseAudioPlayerThread::PulseAudioPlayerThread(AudioProvider *_provider)
|
|
||||||
: wxThread(wxTHREAD_JOINABLE)
|
|
||||||
, play_notify(0, 1)
|
|
||||||
, stop_notify(0, 1)
|
|
||||||
, shutdown_notify(0, 1)
|
|
||||||
, provider(_provider)
|
|
||||||
{
|
{
|
||||||
pa_sample_spec ss;
|
// TODO: use pulse functions
|
||||||
ss.format = PA_SAMPLE_S16LE;
|
return cur_frame;
|
||||||
ss.channels = provider->GetChannels();
|
|
||||||
ss.rate = provider->GetSampleRate();
|
|
||||||
|
|
||||||
s = pa_simple_new(
|
|
||||||
NULL, // default server
|
|
||||||
"Aegisub", // application name
|
|
||||||
PA_STREAM_PLAYBACK,
|
|
||||||
NULL, // default device
|
|
||||||
"Sound", // stream description
|
|
||||||
&ss, // sample format
|
|
||||||
NULL, // default channel map
|
|
||||||
NULL, // default buffering attributes
|
|
||||||
&paerror // store error code
|
|
||||||
);
|
|
||||||
if (!s) throw pa_strerror(paerror);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/////////////////////
|
// Called by PA to notify about contetxt operation completion
|
||||||
// Thread destructor
|
void PulseAudioPlayer::pa_context_success(pa_context *c, int success, PulseAudioPlayer *thread)
|
||||||
PulseAudioPlayerThread::~PulseAudioPlayerThread() {
|
|
||||||
pa_simple_free(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////
|
|
||||||
// Thread entry point
|
|
||||||
wxThread::ExitCode PulseAudioPlayerThread::Entry()
|
|
||||||
{
|
{
|
||||||
// Loop as long as we aren't told to shutdown
|
thread->context_success_val = success;
|
||||||
while (shutdown_notify.TryWait() == wxSEMA_BUSY) {
|
thread->context_success.Post();
|
||||||
// Wait for a playback operation
|
}
|
||||||
if (play_notify.WaitTimeout(100) == wxSEMA_NO_ERROR) {
|
|
||||||
// So playback was posted... now play something
|
|
||||||
play_mutex.Lock();
|
// Called by PA to notify about other context-related stuff
|
||||||
PlaybackLoop();
|
void PulseAudioPlayer::pa_context_notify(pa_context *c, PulseAudioPlayer *thread)
|
||||||
play_mutex.Unlock();
|
{
|
||||||
}
|
thread->cstate = pa_context_get_state(thread->context);
|
||||||
cur_frame = cur_frame / 2;
|
//printf("Context state change: %d\n", thread->cstate);
|
||||||
|
thread->context_notify.Post();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called by PA when an operation completes
|
||||||
|
void PulseAudioPlayer::pa_stream_success(pa_stream *p, int success, PulseAudioPlayer *thread)
|
||||||
|
{
|
||||||
|
thread->stream_success_val = success;
|
||||||
|
thread->stream_success.Post();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called by PA to request more data (and other things?)
|
||||||
|
void PulseAudioPlayer::pa_stream_write(pa_stream *p, size_t length, PulseAudioPlayer *thread)
|
||||||
|
{
|
||||||
|
if (!thread->is_playing) return;
|
||||||
|
if (thread->cur_frame >= thread->end_frame) {
|
||||||
|
thread->is_playing = false;
|
||||||
|
printf("PA requested more buffer, but no more to stream\n");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
printf("PA requested more buffer, %lu bytes\n", (unsigned long)length);
|
||||||
|
unsigned long bpf = thread->bpf;
|
||||||
|
unsigned long frames = length / thread->bpf;
|
||||||
|
unsigned long maxframes = thread->end_frame - thread->cur_frame;
|
||||||
|
if (frames > maxframes) frames = maxframes;
|
||||||
|
printf("Handing it %lu frames\n", frames);
|
||||||
|
void *buf = malloc(frames * bpf);
|
||||||
|
thread->provider->GetAudioWithVolume(buf, thread->cur_frame, frames, thread->volume);
|
||||||
|
::pa_stream_write(p, buf, frames*bpf, free, 0, PA_SEEK_RELATIVE);
|
||||||
|
thread->cur_frame += frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PulseAudioPlayerThread::PlaybackLoop()
|
|
||||||
|
// Called by PA to notify about other stuff
|
||||||
|
void PulseAudioPlayer::pa_stream_notify(pa_stream *p, PulseAudioPlayer *thread)
|
||||||
{
|
{
|
||||||
// Bytes per frame
|
thread->sstate = pa_stream_get_state(thread->stream);
|
||||||
unsigned long bpf = provider->GetChannels() * provider->GetBytesPerSample();
|
//printf("Stream state change: %d\n", thread->sstate);
|
||||||
// Number of frames in a read
|
thread->stream_notify.Post();
|
||||||
unsigned long datalen = provider->GetSampleRate()/8;
|
|
||||||
// Read buffer
|
|
||||||
void *data = malloc(datalen * bpf);
|
|
||||||
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) {
|
|
||||||
pa_simple_flush(s, &paerror);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for end of stream
|
|
||||||
if (cur_pos >= end_pos) {
|
|
||||||
pa_simple_drain(s, &paerror);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write some frames
|
|
||||||
unsigned long to_read = datalen;
|
|
||||||
if (cur_pos + to_read > end_pos)
|
|
||||||
to_read = end_pos - cur_pos;
|
|
||||||
provider->GetAudioWithVolume(data, cur_pos, to_read, vol);
|
|
||||||
if (pa_simple_write(s, data, to_read*bpf, &paerror) < 0) break;
|
|
||||||
cur_pos += to_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PulseAudioPlayerThread::Play(unsigned long _start_frame, unsigned long _num_frames)
|
|
||||||
{
|
|
||||||
StopWait();
|
|
||||||
|
|
||||||
cur_frame = start_frame = _start_frame;
|
|
||||||
end_frame = start_frame + _num_frames;
|
|
||||||
|
|
||||||
play_notify.Post();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PulseAudioPlayerThread::SetEndPosition(unsigned long _end_frame)
|
|
||||||
{
|
|
||||||
parameter_mutex.Lock();
|
|
||||||
end_frame = _end_frame;
|
|
||||||
parameter_mutex.Unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////
|
|
||||||
// Stop playback thread
|
|
||||||
void PulseAudioPlayerThread::Stop()
|
|
||||||
{
|
|
||||||
stop_notify.Post();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PulseAudioPlayerThread::StopWait()
|
|
||||||
{
|
|
||||||
stop_notify.Post();
|
|
||||||
// Now wait for the play mutex to have been freed
|
|
||||||
play_mutex.Lock();
|
|
||||||
play_mutex.Unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PulseAudioPlayerThread::Shutdown()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
shutdown_notify.Post();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PulseAudioPlayerThread::SetVolume(double new_volume)
|
|
||||||
{
|
|
||||||
parameter_mutex.Lock();
|
|
||||||
volume = new_volume;
|
|
||||||
parameter_mutex.Unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
unsigned long PulseAudioPlayerThread::GetStartPosition()
|
|
||||||
{
|
|
||||||
wxMutexLocker lock(parameter_mutex);
|
|
||||||
return start_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
unsigned long PulseAudioPlayerThread::GetEndPosition()
|
|
||||||
{
|
|
||||||
wxMutexLocker lock(parameter_mutex);
|
|
||||||
return end_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
unsigned long PulseAudioPlayerThread::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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool PulseAudioPlayerThread::IsPlaying()
|
|
||||||
{
|
|
||||||
return play_mutex.TryLock() == wxMUTEX_BUSY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue