#include <climits>
#include <cmath>
#include <algorithm>
+#include <cmath>
// Static variable definitions.
const unsigned int RtApi::MAX_SAMPLE_RATES = 14;
{ "ds" , "DirectSound" },
{ "dummy" , "Dummy" },
};
-const unsigned int rtaudio_num_api_names =
+const unsigned int rtaudio_num_api_names =
sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]);
// The order here will control the order of RtAudio's API search in
struct timeval then;
struct timeval now;
- if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 )
+ if ( stream_.state != STREAM_RUNNING || (stream_.lastTickTimestamp.tv_sec == 0 && stream_.lastTickTimestamp.tv_usec == 0) )
return stream_.streamTime;
gettimeofday( &now, NULL );
then = stream_.lastTickTimestamp;
return stream_.streamTime +
((now.tv_sec + 0.000001 * now.tv_usec) -
- (then.tv_sec + 0.000001 * then.tv_usec));
+ (then.tv_sec + 0.000001 * then.tv_usec));
#else
return stream_.streamTime;
#endif
return stream_.sampleRate;
}
+void RtApi :: startStream( void )
+{
+#if defined( HAVE_GETTIMEOFDAY )
+ stream_.lastTickTimestamp.tv_sec = 0;
+ stream_.lastTickTimestamp.tv_usec = 0;
+#endif
+}
+
// *************************************************** //
//
void RtApiCore :: startStream( void )
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiCore::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
channelsLeft -= streamChannels;
}
}
-
+
if ( stream_.doConvertBuffer[1] ) { // convert from our internal "device" buffer
convertBuffer( stream_.userBuffer[1],
stream_.deviceBuffer,
void RtApiJack :: startStream( void )
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiJack::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
// CoInitialize beforehand, but it must be for appartment threading
// (in which case, CoInitilialize will return S_FALSE here).
coInitialized_ = false;
- HRESULT hr = CoInitialize( NULL );
+ HRESULT hr = CoInitialize( NULL );
if ( FAILED(hr) ) {
errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)";
error( RtAudioError::WARNING );
errorText_ = errorStream_.str();
goto error;
}
- buffersAllocated = true;
+ buffersAllocated = true;
stream_.state = STREAM_STOPPED;
// Set flags for buffer conversion.
void RtApiAsio :: startStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiAsio::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
static const char* getAsioErrorString( ASIOError result )
{
- struct Messages
+ struct Messages
{
ASIOError value;
const char*message;
};
- static const Messages m[] =
+ static const Messages m[] =
{
{ ASE_NotPresent, "Hardware input or output is not present or available." },
{ ASE_HWMalfunction, "Hardware is malfunctioning." },
relOutIndex += bufferSize_;
}
- // "in" index can end on the "out" index but cannot begin at it
- if ( inIndex_ < relOutIndex && inIndexEnd > relOutIndex ) {
+ // the "IN" index CAN BEGIN at the "OUT" index
+ // the "IN" index CANNOT END at the "OUT" index
+ if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) {
return false; // not enough space between "in" index and "out" index
}
relInIndex += bufferSize_;
}
- // "out" index can begin at and end on the "in" index
+ // the "OUT" index CANNOT BEGIN at the "IN" index
+ // the "OUT" index CAN END at the "IN" index
if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) {
return false; // not enough space between "out" index and "in" index
}
// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate
// between HW and the user. The WasapiResampler class is used to perform this conversion between
-// HwIn->UserIn and UserOut->HwOut during the stream callback loop.
+// HwIn->UserIn and UserO ut->HwOut during the stream callback loop.
class WasapiResampler
{
public:
hr = audioClient->GetMixFormat( &deviceFormat );
if ( FAILED( hr ) ) {
- errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format.";
+ char error[256];
+ snprintf(error, sizeof(error), "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format (%d)", hr);
+ errorText_ = error;
goto Exit;
}
void RtApiWasapi::startStream( void )
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiWasapi::startStream: The stream is already running.";
#if defined(__WINDOWS_DS__) // Windows DirectSound API
// Modified by Robin Davies, October 2005
-// - Improvements to DirectX pointer chasing.
+// - Improvements to DirectX pointer chasing.
// - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30.
// - Auto-call CoInitialize for DSOUND and ASIO platforms.
// Various revisions for RtAudio 4.0 by Gary Scavone, April 2007
void *id[2];
void *buffer[2];
bool xrun[2];
- UINT bufferPointer[2];
+ UINT bufferPointer[2];
DWORD dsBufferSize[2];
DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by.
HANDLE condition;
void RtApiDs :: startStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiDs::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
// Increase scheduler frequency on lesser windows (a side-effect of
// increasing timer accuracy). On greater windows (Win2K or later),
// this is already in effect.
- timeBeginPeriod( 1 );
+ timeBeginPeriod( 1 );
buffersRolling = false;
duplexPrerollBytes = 0;
void RtApiDs :: stopStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_STOPPED ) {
errorText_ = "RtApiDs::stopStream(): the stream is already stopped!";
error( RtAudioError::WARNING );
}
if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {
-
+
LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
if ( handle->drainCounter > 1 ) { // write zeros to the output stream
}
if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize )
- || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) {
+ || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) {
// We've strayed into the forbidden zone ... resync the read pointer.
handle->xrun[0] = true;
nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes;
if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset
DWORD endRead = nextReadPointer + bufferBytes;
- // Handling depends on whether we are INPUT or DUPLEX.
+ // Handling depends on whether we are INPUT or DUPLEX.
// If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode,
// then a wait here will drag the write pointers into the forbidden zone.
- //
- // In DUPLEX mode, rather than wait, we will back off the read pointer until
- // it's in a safe position. This causes dropouts, but it seems to be the only
- // practical way to sync up the read and write pointers reliably, given the
- // the very complex relationship between phase and increment of the read and write
+ //
+ // In DUPLEX mode, rather than wait, we will back off the read pointer until
+ // it's in a safe position. This causes dropouts, but it seems to be the only
+ // practical way to sync up the read and write pointers reliably, given the
+ // the very complex relationship between phase and increment of the read and write
// pointers.
//
// In order to minimize audible dropouts in DUPLEX mode, we will
error( RtAudioError::SYSTEM_ERROR );
return;
}
-
+
if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset
}
}
// This method calls snd_pcm_prepare if the device isn't already in that state.
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiAlsa::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;
snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;
if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {
- if ( apiInfo->synchronized )
+ if ( apiInfo->synchronized )
result = snd_pcm_drop( handle[0] );
else
result = snd_pcm_drain( handle[0] );
#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread)
if ( info->doRealtime ) {
- std::cerr << "RtAudio alsa: " <<
- (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
+ std::cerr << "RtAudio alsa: " <<
+ (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
"running realtime scheduling" << std::endl;
}
#endif
#include <pulse/error.h>
#include <pulse/simple.h>
+#include <pulse/pulseaudio.h>
#include <cstdio>
static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000,
return 1;
}
+void RtApiPulse::sinkInfoCallback(pa_context*, const pa_sink_info* info, int, void* arg)
+{
+ RtApiPulse* api = (RtApiPulse *) arg;
+ if (info) {
+ api->channels_ = info->sample_spec.channels;
+ }
+ pa_threaded_mainloop_signal(api->mainloop_, 0);
+}
+
+void RtApiPulse::contextStateCallback(pa_context* c, void* arg)
+{
+ pa_threaded_mainloop* mainloop = (pa_threaded_mainloop*) arg;
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(mainloop, 0);
+ break;
+ default:
+ break;
+ }
+}
+
RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ )
{
+ /* Set up some defaults in case we crash and burn */
RtAudio::DeviceInfo info;
info.probed = true;
info.name = "PulseAudio";
info.preferredSampleRate = 48000;
info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32;
+ /* Get the number of output channels from pulseaudio. A simple task, you say?
+ "What is your mainloop?" */
+ mainloop_ = pa_threaded_mainloop_new();
+ if (!mainloop_) {
+ return info;
+ }
+
+ pa_threaded_mainloop_start(mainloop_);
+ pa_threaded_mainloop_lock(mainloop_);
+
+ /* "And what is your context?" */
+ pa_context* context = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), "RtAudio");
+ if (!context) {
+ pa_threaded_mainloop_unlock(mainloop_);
+ pa_threaded_mainloop_stop(mainloop_);
+ pa_threaded_mainloop_free(mainloop_);
+ mainloop_ = 0;
+ return info;
+ }
+
+ pa_context_set_state_callback(context, contextStateCallback, mainloop_);
+
+ pa_context_connect(context, 0, (pa_context_flags_t) 0, 0);
+
+ /* "And what is your favourite colour?" */
+ int connected = 0;
+ pa_context_state_t state = pa_context_get_state(context);
+ for (; !connected; state = pa_context_get_state(context)) {
+ switch (state) {
+ case PA_CONTEXT_READY:
+ connected = 1;
+ continue;
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ /* Blue! No, I mean red! */
+ pa_threaded_mainloop_unlock(mainloop_);
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ pa_threaded_mainloop_stop(mainloop_);
+ pa_threaded_mainloop_free(mainloop_);
+ mainloop_ = 0;
+ return info;
+ default:
+ pa_threaded_mainloop_wait(mainloop_);
+ break;
+ }
+ }
+
+ pa_operation* op = pa_context_get_sink_info_by_index(context, 0, sinkInfoCallback, this);
+
+ if (op) {
+ pa_operation_unref(op);
+ }
+
+ pa_threaded_mainloop_wait(mainloop_);
+ pa_threaded_mainloop_unlock(mainloop_);
+
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+
+ pa_threaded_mainloop_stop(mainloop_);
+ pa_threaded_mainloop_free(mainloop_);
+ mainloop_ = 0;
+
+ info.outputChannels = channels_;
+
return info;
}
CallbackInfo *cbi = static_cast<CallbackInfo *>( user );
RtApiPulse *context = static_cast<RtApiPulse *>( cbi->object );
volatile bool *isRunning = &cbi->isRunning;
-
+
#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread)
if (cbi->doRealtime) {
- std::cerr << "RtAudio pulse: " <<
- (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
+ std::cerr << "RtAudio pulse: " <<
+ (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
"running realtime scheduling" << std::endl;
}
#endif
-
+
while ( *isRunning ) {
pthread_testcancel();
context->callbackEvent();
else
bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize *
formatBytes( stream_.userFormat );
-
+
if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) {
errorStream_ << "RtApiPulse::callbackEvent: audio read error, " <<
pa_strerror( pa_error ) << ".";
MUTEX_UNLOCK( &stream_.mutex );
RtApi::tickStreamTime();
+ if (pah->s_play) {
+ int e = 0;
+ pa_usec_t const lat = pa_simple_get_latency(pah->s_play, &e);
+ if (e == 0) {
+ stream_.latency[0] = lat * stream_.sampleRate / 1000000;
+ }
+ }
+
if ( doStopStream == 1 )
stopStream();
}
void RtApiPulse::startStream( void )
{
+ RtApi::startStream();
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );
if ( stream_.state == STREAM_CLOSED ) {
}
stream_.state = STREAM_STOPPED;
+ pah->runnable = false;
MUTEX_LOCK( &stream_.mutex );
if ( pah && pah->s_play ) {
}
stream_.state = STREAM_STOPPED;
+ pah->runnable = false;
MUTEX_LOCK( &stream_.mutex );
if ( pah && pah->s_play ) {
if ( device != 0 ) return false;
if ( mode != INPUT && mode != OUTPUT ) return false;
- if ( channels != 1 && channels != 2 ) {
- errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels.";
- return false;
- }
ss.channels = channels;
if ( firstChannel != 0 ) return false;
}
break;
case OUTPUT:
- pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error );
+ /* XXX: hard-coded for DCP-o-matic */
+ pa_channel_map map;
+ pa_channel_map_init(&map);
+ /* XXX: need to check 7.1 */
+ map.channels = channels;
+
+ if (channels > 0) {
+ map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ }
+ if (channels > 1) {
+ map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ }
+ if (channels > 2) {
+ map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ }
+ if (channels > 3) {
+ map.map[3] = PA_CHANNEL_POSITION_LFE;
+ }
+ if (channels > 4) {
+ map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
+ }
+ if (channels > 5) {
+ map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ }
+ if (channels > 6) {
+ map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ }
+ if (channels > 7) {
+ map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ }
+
+ pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, &map, NULL, &error );
if ( !pah->s_play ) {
errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server.";
goto error;
if ( !stream_.callbackInfo.isRunning ) {
stream_.callbackInfo.object = this;
-
+
stream_.state = STREAM_STOPPED;
// Set the thread attributes for joinable and realtime scheduling
// priority (optional). The higher priority will only take affect
if ( priority < min ) priority = min;
else if ( priority > max ) priority = max;
param.sched_priority = priority;
-
+
// Set the policy BEFORE the priority. Otherwise it fails.
pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
}
return SUCCESS;
-
+
error:
if ( pah && stream_.callbackInfo.isRunning ) {
pthread_cond_destroy( &pah->runnable_cv );
if ( priority < min ) priority = min;
else if ( priority > max ) priority = max;
param.sched_priority = priority;
-
+
// Set the policy BEFORE the priority. Otherwise it fails.
pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
void RtApiOss :: startStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiOss::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread)
if (info->doRealtime) {
- std::cerr << "RtAudio oss: " <<
- (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
+ std::cerr << "RtAudio oss: " <<
+ (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
"running realtime scheduling" << std::endl;
}
#endif
// End:
//
// vim: et sts=2 sw=2
-