use callback model instead of blocking read/write.

fixes delay on linux.
currently not implementing the overflow bit, not sure it's needed since we manage our own buffer now.
This commit is contained in:
Shawn Hoffman 2011-10-16 23:20:37 -07:00
parent 1d6bd3248c
commit c528978608
2 changed files with 88 additions and 30 deletions

View File

@ -40,19 +40,64 @@ void CEXIMic::StreamInit()
// Setup the wonderful c-interfaced lib... // Setup the wonderful c-interfaced lib...
if ((pa_error = Pa_Initialize()) != paNoError) if ((pa_error = Pa_Initialize()) != paNoError)
StreamLog("Pa_Initialize"); StreamLog("Pa_Initialize");
stream_wpos = stream_rpos = 0;
memset(stream_buffer, 0, stream_size * sample_size);
} }
void CEXIMic::StreamTerminate() void CEXIMic::StreamTerminate()
{ {
Pa_AbortStream(pa_stream);
if ((pa_error = Pa_Terminate()) != paNoError) if ((pa_error = Pa_Terminate()) != paNoError)
StreamLog("Pa_Terminate"); StreamLog("Pa_Terminate");
} }
static int Pa_Callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
(void)outputBuffer;
(void)timeInfo;
CEXIMic *mic = (CEXIMic *)userData;
std::lock_guard<std::mutex> lk(mic->ring_lock);
if (mic->stream_wpos + mic->buff_size_samples >= mic->stream_size)
mic->stream_wpos = 0;
s16 *buff_in = (s16 *)inputBuffer;
s16 *buff_out = &mic->stream_buffer[mic->stream_wpos];
if (buff_in == NULL)
{
for (int i = 0; i < mic->buff_size_samples; i++)
{
buff_out[i] = 0;
}
}
else
{
for (int i = 0; i < mic->buff_size_samples; i++)
{
buff_out[i] = buff_in[i];
}
}
mic->stream_wpos += mic->buff_size_samples;
mic->stream_wpos %= mic->stream_size;
return paContinue;
}
void CEXIMic::StreamStart() void CEXIMic::StreamStart()
{ {
// Open stream with current parameters // Open stream with current parameters
pa_error = Pa_OpenDefaultStream(&pa_stream, 1, 0, paInt16, pa_error = Pa_OpenDefaultStream(&pa_stream, 1, 0, paInt16,
sample_rate, buff_size_samples, NULL, NULL); sample_rate, buff_size_samples, Pa_Callback, this);
StreamLog("Pa_OpenDefaultStream"); StreamLog("Pa_OpenDefaultStream");
pa_error = Pa_StartStream(pa_stream); pa_error = Pa_StartStream(pa_stream);
StreamLog("Pa_StartStream"); StreamLog("Pa_StartStream");
@ -67,23 +112,26 @@ void CEXIMic::StreamStop()
void CEXIMic::StreamReadOne() void CEXIMic::StreamReadOne()
{ {
// Returns num samples or error std::lock_guard<std::mutex> lk(ring_lock);
pa_error = Pa_GetStreamReadAvailable(pa_stream);
if (pa_error >= buff_size_samples)
{
pa_error = Pa_ReadStream(pa_stream, ring_buffer, buff_size_samples);
if (pa_error == paInputOverflowed) int samples_avail = (stream_wpos > stream_rpos) ?
{ stream_wpos - stream_rpos :
status.buff_ovrflw = 1; stream_size - stream_rpos + stream_wpos;
// Input overflowed - is re-setting the stream the only to recover?
StreamStop(); if (samples_avail >= buff_size_samples)
StreamStart(); {
} s16 *last_buffer = &stream_buffer[stream_rpos];
else if (pa_error != paNoError) memcpy(ring_buffer, last_buffer, buff_size);
{
StreamLog("Pa_ReadStream"); stream_rpos += buff_size_samples;
} stream_rpos %= stream_size;
// TODO: if overflow bit matters, find a nice way
//if (samples_avail >= buff_size_samples * 2)
//{
// status.buff_ovrflw = 1;
// stream_rpos = stream_wpos = 0;
//}
} }
} }
@ -91,13 +139,11 @@ void CEXIMic::StreamReadOne()
// This works by opening and starting a portaudio input stream when the is_active // This works by opening and starting a portaudio input stream when the is_active
// bit is set. The interrupt is scheduled in the future based on sample rate and // bit is set. The interrupt is scheduled in the future based on sample rate and
// buffer size settings. When the console handles the interrupt, it will send // buffer size settings. When the console handles the interrupt, it will send
// cmdGetBuffer, which is when we actually read data from a buffer portaudio fills // cmdGetBuffer, which is when we actually read data from a buffer filled
// in the background (ie on demand instead of realtime). Because of this we need // in the background by Pa_Callback.
// to clear portaudio's buffer if emulation speed drops below realtime, or else
// a bad audio lag develops. It's actually kind of convenient because it gives
// us a way to detect if buff_ovrflw should be set.
u8 const CEXIMic::exi_id[] = { 0, 0x0a, 0, 0, 0 }; u8 const CEXIMic::exi_id[] = { 0, 0x0a, 0, 0, 0 };
int const CEXIMic::stream_size = sizeof(stream_buffer) / sizeof(*stream_buffer);
CEXIMic::CEXIMic(int index) CEXIMic::CEXIMic(int index)
: slot(index) : slot(index)
@ -211,13 +257,13 @@ void CEXIMic::TransferByte(u8 &byte)
break; break;
case cmdGetBuffer: case cmdGetBuffer:
if (ring_pos == 0)
{ {
// Can set buff_ovrflw if (ring_pos == 0)
StreamReadOne(); StreamReadOne();
}
byte = ring_buffer[ring_pos ^ 1]; byte = ring_buffer[ring_pos ^ 1];
ring_pos = (ring_pos + 1) % buff_size; ring_pos = (ring_pos + 1) % buff_size;
}
break; break;
default: default:

View File

@ -20,6 +20,8 @@
#if HAVE_PORTAUDIO #if HAVE_PORTAUDIO
#include "StdMutex.h"
class CEXIMic : public IEXIDevice class CEXIMic : public IEXIDevice
{ {
public: public:
@ -68,11 +70,6 @@ private:
}; };
UStatus status; UStatus status;
// status bits converted to nice numbers
int sample_rate;
int buff_size;
int buff_size_samples;
// 64 is the max size, can be 16 or 32 as well // 64 is the max size, can be 16 or 32 as well
int ring_pos; int ring_pos;
u8 ring_buffer[64 * sample_size]; u8 ring_buffer[64 * sample_size];
@ -93,6 +90,21 @@ private:
void StreamStop(); void StreamStop();
void StreamReadOne(); void StreamReadOne();
public:
std::mutex ring_lock;
// status bits converted to nice numbers
int sample_rate;
int buff_size;
int buff_size_samples;
// Arbitrarily small ringbuffer used by audio input backend in order to
// keep delay tolerable
s16 stream_buffer[64 * sample_size * 500];
static int const stream_size;
int stream_wpos;
int stream_rpos;
protected: protected:
virtual void TransferByte(u8 &byte); virtual void TransferByte(u8 &byte);
}; };