Aegisub/aegisub/audio_player_portaudio.cpp
2007-08-29 13:36:30 +00:00

276 lines
7.2 KiB
C++

// Copyright (c) 2005-2007, Rodrigo Braz Monteiro
// 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
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:zeratul@cellosoft.com
//
///////////
// Headers
#include "audio_player.h"
#include "audio_provider.h"
#include "utils.h"
extern "C" {
#include <portaudio.h>
}
#ifdef HAVE_PA_GETSTREAMTIME
#define Pa_StreamTime Pa_GetStreamTime /* PortAudio v19 */
#define PaTimestamp PaTime
#endif
///////////
// Library
#if __VISUALC__ >= 1200
#pragma comment(lib,"portaudio.lib")
#endif
////////////////////
// Portaudio player
class PortAudioPlayer : public AudioPlayer {
private:
static int pa_refcount;
wxMutex PAMutex;
volatile bool stopping;
//bool softStop;
bool playing;
float volume;
volatile long long playPos;
volatile long long startPos;
volatile long long endPos;
void *stream;
PaTimestamp paStart;
volatile long long realPlayPos;
#ifndef HAVE_PA_GETSTREAMTIME
static int paCallback(void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *userData);
#else
static int paCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timei,
PaStreamCallbackFlags flags, void *userData);
#endif
public:
PortAudioPlayer();
~PortAudioPlayer();
void OpenStream();
void CloseStream();
void Play(long long start,long long count);
void Stop(bool timerToo=true);
bool IsPlaying() { return playing; }
long long GetStartPosition() { return startPos; }
long long GetEndPosition() { return endPos; }
long long GetCurrentPosition() { return realPlayPos; }
void SetEndPosition(long long pos) { endPos = pos; }
void SetCurrentPosition(long long pos) { playPos = pos; realPlayPos = pos; }
void SetVolume(double vol) { volume = vol; }
double GetVolume() { return volume; }
wxMutex *GetMutex() { return &PAMutex; }
};
///////////
// Factory
class PortAudioPlayerFactory : public AudioPlayerFactory {
public:
AudioPlayer *CreatePlayer() { return new PortAudioPlayer(); }
PortAudioPlayerFactory() : AudioPlayerFactory(_T("portaudio")) {}
} registerPortAudioPlayer;
/////////////////////
// Reference counter
int PortAudioPlayer::pa_refcount = 0;
///////////////
// Constructor
PortAudioPlayer::PortAudioPlayer() {
// Initialize portaudio
if (!pa_refcount) {
PaError err = Pa_Initialize();
if (err != paNoError) {
static wchar_t errormsg[2048];
swprintf(errormsg, 2048, L"Failed opening PortAudio: %s", Pa_GetErrorText(err));
throw (const wchar_t *)errormsg;
}
pa_refcount++;
}
// Variables
playing = false;
stopping = false;
volume = 1.0f;
paStart = 0.0;
}
//////////////
// Destructor
PortAudioPlayer::~PortAudioPlayer() {
// Deinit portaudio
if (!--pa_refcount) Pa_Terminate();
}
//////////////////////
// PortAudio callback
#ifndef HAVE_PA_GETSTREAMTIME
int PortAudioPlayer::paCallback(void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, PaTimestamp outTime, void *userData) {
#else
int PortAudioPlayer::paCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timei,
PaStreamCallbackFlags flags, void *userData) {
#endif
// Get provider
PortAudioPlayer *player = (PortAudioPlayer *) userData;
AudioProvider *provider = player->GetProvider();
int end = 0;
// Calculate how much left
long long lenAvailable = player->endPos - player->playPos;
unsigned long long avail = 0;
if (lenAvailable > 0) {
avail = lenAvailable;
if (avail > framesPerBuffer) {
lenAvailable = framesPerBuffer;
avail = lenAvailable;
}
}
else {
lenAvailable = 0;
avail = 0;
}
// Play something
if (lenAvailable > 0) {
provider->GetAudio(outputBuffer,player->playPos,lenAvailable);
}
// Set volume
short *output = (short*) outputBuffer;
for (unsigned int i=0;i<avail;i++) output[i] = MID(-(1<<15),int(output[i] * player->GetVolume()),(1<<15)-1);
// Fill rest with blank
for (unsigned int i=avail;i<framesPerBuffer;i++) output[i]=0;
// Set play position (and real one)
player->playPos += framesPerBuffer;
#ifndef __APPLE__
player->realPlayPos = (long long)(Pa_StreamTime(player->stream) - player->paStart) + player->startPos;
#else
// AudioDeviceGetCurrentTime(), used by Pa_StreamTime() on OS X, is buggered, so use playPos for now
player->realPlayPos = player->playPos;
#endif
// Cap to start if lower
return end;
}
////////
// Play
void PortAudioPlayer::Play(long long start,long long count) {
// Stop if it's already playing
wxMutexLocker locker(PAMutex);
// Set values
endPos = start + count;
playPos = start;
realPlayPos = start;
startPos = start;
// Start playing
if (!playing) {
PaError err = Pa_StartStream(stream);
if (err != paNoError) {
return;
}
}
playing = true;
paStart = Pa_StreamTime(stream);
// Update timer
if (displayTimer && !displayTimer->IsRunning()) displayTimer->Start(15);
}
////////
// Stop
void PortAudioPlayer::Stop(bool timerToo) {
//wxMutexLocker locker(PAMutex);
//softStop = false;
// Stop stream
playing = false;
Pa_StopStream (stream);
// Stop timer
if (timerToo && displayTimer) {
displayTimer->Stop();
}
}
///////////////
// Open stream
void PortAudioPlayer::OpenStream() {
// Open stream
PaError err = Pa_OpenDefaultStream(&stream,0,provider->GetChannels(),paInt16,provider->GetSampleRate(),256,
#ifndef HAVE_PA_GETSTREAMTIME
16, /* Pa v19 doesn't have a numberOfBuffers parameter */
#endif
paCallback,this);
if (err != paNoError) {
throw wxString(_T("Failed initializing PortAudio stream with error: ") + wxString(Pa_GetErrorText(err),wxConvLocal));
}
}
///////////////
// Close stream
void PortAudioPlayer::CloseStream() {
try {
Stop(false);
Pa_CloseStream(stream);
} catch (...) {}
}