forked from mia/Aegisub
Finish rewriting the PortAudio player
Remove pointless volatiles. Throw agi::Exception-derived exceptions rather than bare strings. Eliminate pointless struct which wrapped a few member variables for no apparent reason. Use logging statements rather than printf. Don't set an explicit frame buffer size as the audio providers are fine with variable sizes and portaudio strongly recommends leaving it up to the device to decide. Closes #997. Originally committed to SVN as r5918.
This commit is contained in:
parent
c55195e11c
commit
87496b8767
2 changed files with 104 additions and 141 deletions
|
@ -27,7 +27,7 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
//
|
||||
// $Id$
|
||||
// $Id: audio_player_portaudio.cpp 5897 2011-11-20 03:43:52Z plorkyeran $
|
||||
|
||||
/// @file audio_player_portaudio.cpp
|
||||
/// @brief PortAudio v18-based audio output
|
||||
|
@ -48,179 +48,137 @@
|
|||
#include "charset_conv.h"
|
||||
#include "main.h"
|
||||
#include "utils.h"
|
||||
#include <wx/log.h>
|
||||
|
||||
// Uncomment to enable extremely spammy debug logging
|
||||
//#define PORTAUDIO_DEBUG
|
||||
|
||||
// Init reference counter
|
||||
int PortAudioPlayer::pa_refcount = 0;
|
||||
|
||||
/// @brief Constructor
|
||||
PortAudioPlayer::PortAudioPlayer() {
|
||||
// Initialize portaudio
|
||||
if (!pa_refcount) {
|
||||
PaError err = Pa_Initialize();
|
||||
|
||||
if (err != paNoError) {
|
||||
static char errormsg[2048];
|
||||
snprintf(errormsg, 2048, "Failed opening PortAudio: %s", Pa_GetErrorText(err));
|
||||
throw errormsg;
|
||||
}
|
||||
if (err != paNoError)
|
||||
throw PortAudioError(std::string("Failed opening PortAudio:") + Pa_GetErrorText(err));
|
||||
pa_refcount++;
|
||||
}
|
||||
|
||||
volume = 1.0f;
|
||||
pos.pa_start = 0.0;
|
||||
pa_start = 0.0;
|
||||
}
|
||||
|
||||
|
||||
/// @brief Destructor
|
||||
PortAudioPlayer::~PortAudioPlayer() {
|
||||
// Deinit portaudio
|
||||
if (!--pa_refcount) Pa_Terminate();
|
||||
}
|
||||
|
||||
|
||||
/// @brief Open stream
|
||||
void PortAudioPlayer::OpenStream() {
|
||||
// Open stream
|
||||
PaStreamParameters pa_output_p;
|
||||
PaDeviceIndex pa_device = OPT_GET("Player/Audio/PortAudio/Device")->GetInt();
|
||||
|
||||
int pa_config_default = OPT_GET("Player/Audio/PortAudio/Device")->GetInt();
|
||||
PaDeviceIndex pa_device;
|
||||
|
||||
if (pa_config_default < 0) {
|
||||
if (pa_device < 0) {
|
||||
pa_device = Pa_GetDefaultOutputDevice();
|
||||
LOG_D("audio/player/portaudio") << "using default output device:" << pa_device;
|
||||
} else {
|
||||
pa_device = pa_config_default;
|
||||
LOG_D("audio/player/portaudio") << "using config device: " << pa_device;
|
||||
}
|
||||
else
|
||||
LOG_D("audio/player/portaudio") << "using config device: " << pa_device;
|
||||
|
||||
PaStreamParameters pa_output_p;
|
||||
pa_output_p.device = pa_device;
|
||||
pa_output_p.channelCount = provider->GetChannels();
|
||||
pa_output_p.sampleFormat = paInt16;
|
||||
pa_output_p.suggestedLatency = Pa_GetDeviceInfo(pa_device)->defaultLowOutputLatency;
|
||||
pa_output_p.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
LOG_D("audio/player/portaudio") << "output channels: " << pa_output_p.channelCount << ", latency: " << pa_output_p.suggestedLatency << " sample rate: " << pa_output_p.sampleFormat;
|
||||
LOG_D("audio/player/portaudio") << "OpenStream:"
|
||||
<< " output channels: " << pa_output_p.channelCount
|
||||
<< " latency: " << pa_output_p.suggestedLatency
|
||||
<< " sample rate: " << pa_output_p.sampleFormat;
|
||||
|
||||
PaError err = Pa_OpenStream(&stream, NULL, &pa_output_p, provider->GetSampleRate(), 256, paPrimeOutputBuffersUsingStreamCallback, paCallback, this);
|
||||
PaError err = Pa_OpenStream(&stream, NULL, &pa_output_p, provider->GetSampleRate(), 0, paPrimeOutputBuffersUsingStreamCallback, paCallback, this);
|
||||
|
||||
if (err != paNoError) {
|
||||
|
||||
const PaHostErrorInfo *pa_err = Pa_GetLastHostErrorInfo();
|
||||
LOG_D_IF(pa_err->errorCode != 0, "audio/player/portaudio") << "HostError: API: " << pa_err->hostApiType << ", " << pa_err->errorText << ", " << pa_err->errorCode;
|
||||
LOG_D("audio/player/portaudio") << "Failed initializing PortAudio stream with error: " << Pa_GetErrorText(err);
|
||||
throw wxString("Failed initializing PortAudio stream with error: " + wxString(Pa_GetErrorText(err),csConvLocal));
|
||||
throw PortAudioError("Failed initializing PortAudio stream with error: " + std::string(Pa_GetErrorText(err)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @brief Close stream
|
||||
void PortAudioPlayer::CloseStream() {
|
||||
Stop(false);
|
||||
Pa_CloseStream(stream);
|
||||
}
|
||||
|
||||
|
||||
/// @brief Called when the callback has finished.
|
||||
/// @param userData Local data to be handed to the callback.
|
||||
void PortAudioPlayer::paStreamFinishedCallback(void *userData) {
|
||||
PortAudioPlayer *player = (PortAudioPlayer *) userData;
|
||||
|
||||
if (player->displayTimer) {
|
||||
if (player->displayTimer)
|
||||
player->displayTimer->Stop();
|
||||
}
|
||||
LOG_D("audio/player/portaudio") << "stopping stream";
|
||||
}
|
||||
|
||||
|
||||
/// @brief Play audio.
|
||||
/// @param start Start position.
|
||||
/// @param count Frame count
|
||||
void PortAudioPlayer::Play(int64_t start,int64_t count) {
|
||||
PaError err;
|
||||
|
||||
// Set values
|
||||
pos.end = start + count;
|
||||
pos.current = start;
|
||||
pos.start = start;
|
||||
void PortAudioPlayer::Play(int64_t start_sample, int64_t count) {
|
||||
current = start_sample;
|
||||
start = start_sample;
|
||||
end = start_sample + count;
|
||||
|
||||
// Start playing
|
||||
if (!IsPlaying()) {
|
||||
|
||||
err = Pa_SetStreamFinishedCallback(stream, paStreamFinishedCallback);
|
||||
|
||||
PaError err = Pa_SetStreamFinishedCallback(stream, paStreamFinishedCallback);
|
||||
if (err != paNoError) {
|
||||
LOG_D("audio/player/portaudio") << "could not set FinishedCallback";
|
||||
return;
|
||||
}
|
||||
|
||||
err = Pa_StartStream(stream);
|
||||
|
||||
if (err != paNoError) {
|
||||
LOG_D("audio/player/portaudio") << "error playing stream";
|
||||
return;
|
||||
}
|
||||
}
|
||||
pos.pa_start = Pa_GetStreamTime(stream);
|
||||
pa_start = Pa_GetStreamTime(stream);
|
||||
|
||||
// Update timer
|
||||
if (displayTimer && !displayTimer->IsRunning()) displayTimer->Start(15);
|
||||
if (displayTimer && !displayTimer->IsRunning())
|
||||
displayTimer->Start(15);
|
||||
}
|
||||
|
||||
|
||||
/// @brief Stop Playback
|
||||
/// @param timerToo Stop display timer?
|
||||
///
|
||||
void PortAudioPlayer::Stop(bool timerToo) {
|
||||
// Stop stream
|
||||
Pa_StopStream(stream);
|
||||
|
||||
// Stop timer
|
||||
if (timerToo && displayTimer) {
|
||||
if (timerToo && displayTimer)
|
||||
displayTimer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @brief PortAudio callback, used to fill buffer for playback, and prime the playback buffer.
|
||||
/// @param inputBuffer Input buffer.
|
||||
/// @param outputBuffer Output buffer.
|
||||
/// @param framesPerBuffer Frames per buffer.
|
||||
/// @param timeInfo PortAudio time information.
|
||||
/// @param statusFlags Status flags
|
||||
/// @param userData Local data to hand callback
|
||||
/// @return Whether to stop playback.
|
||||
///
|
||||
int PortAudioPlayer::paCallback(
|
||||
const void *inputBuffer,
|
||||
void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags,
|
||||
void *userData) {
|
||||
|
||||
// Get provider
|
||||
int PortAudioPlayer::paCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData)
|
||||
{
|
||||
PortAudioPlayer *player = (PortAudioPlayer *)userData;
|
||||
AudioProvider *provider = player->GetProvider();
|
||||
|
||||
#ifdef PORTAUDIO_DEBUG
|
||||
printf("paCallBack: pos.current: %lld pos.start: %lld paStart: %f Pa_GetStreamTime: %f AdcTime: %f DacTime: %f framesPerBuffer: %lu CPU: %f\n",
|
||||
player->pos.current, player->pos.start, player->paStart, Pa_GetStreamTime(player->stream),
|
||||
timeInfo->inputBufferAdcTime, timeInfo->outputBufferDacTime, framesPerBuffer, Pa_GetStreamCpuLoad(player->stream));
|
||||
LOG_D("audio/player/portaudio") << "psCallback:"
|
||||
<< " current: " << player->current
|
||||
<< " start: " << player->start
|
||||
<< " pa_start: " << player->pa_start
|
||||
<< " currentTime: " << timeInfo->currentTime
|
||||
<< " AdcTime: " << timeInfo->inputBufferAdcTime
|
||||
<< " DacTime: " << timeInfo->outputBufferDacTime
|
||||
<< " framesPerBuffer: " << framesPerBuffer
|
||||
<< " CPU: " << Pa_GetStreamCpuLoad(player->stream);
|
||||
#endif
|
||||
|
||||
// Calculate how much left
|
||||
int64_t lenAvailable = (player->pos.end - player->pos.current) > 0 ? framesPerBuffer : 0;
|
||||
int64_t lenAvailable = std::min<int64_t>(player->end - player->current, framesPerBuffer);
|
||||
|
||||
// Play something
|
||||
if (lenAvailable > 0) {
|
||||
provider->GetAudioWithVolume(outputBuffer, player->pos.current, lenAvailable, player->GetVolume());
|
||||
player->GetProvider()->GetAudioWithVolume(outputBuffer, player->current, lenAvailable, player->GetVolume());
|
||||
|
||||
// Set play position
|
||||
player->pos.current += framesPerBuffer;
|
||||
player->current += lenAvailable;
|
||||
|
||||
// Continue as normal
|
||||
return 0;
|
||||
|
@ -230,54 +188,43 @@ int PortAudioPlayer::paCallback(
|
|||
return paAbort;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// @brief Get current stream position.
|
||||
/// @return Stream position
|
||||
int64_t PortAudioPlayer::GetCurrentPosition()
|
||||
{
|
||||
|
||||
int64_t PortAudioPlayer::GetCurrentPosition() {
|
||||
if (!IsPlaying()) return 0;
|
||||
|
||||
const PaStreamInfo* streamInfo = Pa_GetStreamInfo(stream);
|
||||
PaTime pa_time = Pa_GetStreamTime(stream);
|
||||
int64_t real = (pa_time - pa_start) * provider->GetSampleRate() + start;
|
||||
|
||||
// If portaudio isn't giving us time info then estimate based on buffer fill and current latency
|
||||
if (pa_time == 0 && pa_start == 0)
|
||||
real = current - Pa_GetStreamInfo(stream)->outputLatency * provider->GetSampleRate();
|
||||
|
||||
#ifdef PORTAUDIO_DEBUG
|
||||
PaTime pa_getstream = Pa_GetStreamTime(stream);
|
||||
int64_t real = ((pa_getstream - paStart) * streamInfo->sampleRate) + pos.start;
|
||||
printf("GetCurrentPosition: Pa_GetStreamTime: %f pos.start: %lld pos.current: %lld paStart: %f real: %lld diff: %f\n",
|
||||
pa_getstream, pos.start, pos.current, paStart, real, pa_getstream-paStart);
|
||||
|
||||
return real;
|
||||
LOG_D("audio/player/portaudio") << "GetCurrentPosition:"
|
||||
<< " pa_time: " << pa_time
|
||||
<< " start: " << start
|
||||
<< " current: " << current
|
||||
<< " pa_start: " << pa_start
|
||||
<< " real: " << real
|
||||
<< " diff: " << pa_time - pa_start;
|
||||
#endif
|
||||
|
||||
return ((Pa_GetStreamTime(stream) - pos.pa_start) * streamInfo->sampleRate) + pos.start;
|
||||
|
||||
return real;
|
||||
}
|
||||
|
||||
wxArrayString PortAudioPlayer::GetOutputDevices() {
|
||||
PortAudioPlayer player; // temp player to ensure PA is initialized
|
||||
|
||||
/// @brief Get list of available output devices
|
||||
/// @param favorite Favorite output device
|
||||
/// @return List of available output devices with the 'favorite' being first in the list.
|
||||
wxArrayString PortAudioPlayer::GetOutputDevices(wxString favorite) {
|
||||
wxArrayString list;
|
||||
int devices = Pa_GetDeviceCount();
|
||||
int i;
|
||||
|
||||
if (devices < 0) {
|
||||
// some error here
|
||||
}
|
||||
|
||||
for (i=0; i<devices; i++) {
|
||||
const PaDeviceInfo *dev_info = Pa_GetDeviceInfo(i);
|
||||
wxString name(dev_info->name, wxConvUTF8);
|
||||
list.Insert(name, i);
|
||||
wxArrayString list;
|
||||
for (int i = 0; i < devices; i++) {
|
||||
list.push_back(wxString(Pa_GetDeviceInfo(i)->name, wxConvUTF8));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
bool PortAudioPlayer::IsPlaying() {
|
||||
return Pa_IsStreamActive(stream) ? true : false;
|
||||
return !!Pa_IsStreamActive(stream);
|
||||
}
|
||||
|
||||
#endif // WITH_PORTAUDIO
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
//
|
||||
// $Id$
|
||||
// $Id: audio_player_portaudio.h 4719 2010-08-02 08:03:58Z plorkyeran $
|
||||
|
||||
/// @file audio_player_portaudio.h
|
||||
/// @see audio_player_portaudio.cpp
|
||||
|
@ -39,35 +39,37 @@
|
|||
#include "include/aegisub/audio_player.h"
|
||||
#include "include/aegisub/audio_provider.h"
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
|
||||
extern "C" {
|
||||
#include <portaudio.h>
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_EXCEPTION_NOINNER(PortAudioError, agi::Exception, "audio/player/portaudio")
|
||||
|
||||
/// @class PortAudioPlayer
|
||||
/// @brief PortAudio Player
|
||||
///
|
||||
class PortAudioPlayer : public AudioPlayer {
|
||||
private:
|
||||
|
||||
/// PortAudio initilisation reference counter.
|
||||
/// PortAudio initilisation reference counter
|
||||
static int pa_refcount;
|
||||
|
||||
/// Current volume level.
|
||||
float volume;
|
||||
float volume; ///< Current volume level
|
||||
int64_t current; ///< Current position
|
||||
int64_t start; ///< Start position
|
||||
int64_t end; ///< End position
|
||||
PaTime pa_start; ///< PortAudio internal start position
|
||||
|
||||
/// @brief Stream playback position info.
|
||||
struct PositionInfo {
|
||||
volatile int64_t current; /// Current position.
|
||||
volatile int64_t start; /// Start position.
|
||||
volatile int64_t end; /// End position.
|
||||
PaTime pa_start; /// PortAudio internal start position.
|
||||
};
|
||||
PositionInfo pos;
|
||||
|
||||
/// PortAudio stream.
|
||||
void *stream;
|
||||
PaStream *stream; ///< PortAudio stream
|
||||
|
||||
/// @brief PortAudio callback, used to fill buffer for playback, and prime the playback buffer.
|
||||
/// @param inputBuffer Input buffer.
|
||||
/// @param outputBuffer Output buffer.
|
||||
/// @param framesPerBuffer Frames per buffer.
|
||||
/// @param timeInfo PortAudio time information.
|
||||
/// @param statusFlags Status flags
|
||||
/// @param userData Local data to hand callback
|
||||
/// @return Whether to stop playback.
|
||||
static int paCallback(
|
||||
const void *inputBuffer,
|
||||
void *outputBuffer,
|
||||
|
@ -78,16 +80,27 @@ private:
|
|||
statusFlags,
|
||||
void *userData);
|
||||
|
||||
/// @brief Called when the callback has finished.
|
||||
/// @param userData Local data to be handed to the callback.
|
||||
static void paStreamFinishedCallback(void *userData);
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
PortAudioPlayer();
|
||||
/// @brief Destructor
|
||||
~PortAudioPlayer();
|
||||
|
||||
/// @brief Open stream
|
||||
void OpenStream();
|
||||
/// @brief Close stream
|
||||
void CloseStream();
|
||||
|
||||
/// @brief Play audio.
|
||||
/// @param start Start position.
|
||||
/// @param count Frame count
|
||||
void Play(int64_t start,int64_t count);
|
||||
/// @brief Stop Playback
|
||||
/// @param timerToo Stop display timer?
|
||||
void Stop(bool timerToo=true);
|
||||
|
||||
/// @brief Whether audio is currently being played.
|
||||
|
@ -96,20 +109,22 @@ public:
|
|||
|
||||
/// @brief Position audio will be played from.
|
||||
/// @return Start position.
|
||||
int64_t GetStartPosition() { return pos.start; }
|
||||
int64_t GetStartPosition() { return start; }
|
||||
|
||||
/// @brief End position playback will stop at.
|
||||
/// @return End position.
|
||||
int64_t GetEndPosition() { return pos.end; }
|
||||
int64_t GetEndPosition() { return end; }
|
||||
/// @brief Get current stream position.
|
||||
/// @return Stream position
|
||||
int64_t GetCurrentPosition();
|
||||
|
||||
/// @brief Set end position of playback
|
||||
/// @param pos End position
|
||||
void SetEndPosition(int64_t position) { pos.end = position; }
|
||||
void SetEndPosition(int64_t position) { end = position; }
|
||||
|
||||
/// @brief Set current position of playback.
|
||||
/// @param pos Current position
|
||||
void SetCurrentPosition(int64_t position) { pos.current = position; }
|
||||
void SetCurrentPosition(int64_t position) { current = position; }
|
||||
|
||||
|
||||
/// @brief Set volume level
|
||||
|
@ -120,6 +135,7 @@ public:
|
|||
/// @return Volume level
|
||||
double GetVolume() { return volume; }
|
||||
|
||||
wxArrayString GetOutputDevices(wxString favorite);
|
||||
/// Get list of available output devices
|
||||
static wxArrayString GetOutputDevices();
|
||||
};
|
||||
#endif //ifdef WITH_PORTAUDIO
|
||||
|
|
Loading…
Reference in a new issue