Remove old, buggy driver.

This commit is contained in:
Steve Checkoway
2006-12-30 04:31:55 +00:00
parent bdda436ce7
commit ea988bab1c
2 changed files with 0 additions and 615 deletions
@@ -1,535 +0,0 @@
#include "global.h"
#include "RageUtil.h"
#include "RageSoundDriver_CA.h"
#include "RageLog.h"
#include "RageTimer.h"
#include "PrefsManager.h"
#include "archutils/Darwin/DarwinThreadHelpers.h"
REGISTER_SOUND_DRIVER_CLASS2( CoreAudio, CA );
static const UInt8 kAudioDeviceSectionInput = 0x01;
static const UInt8 kAudioDeviceSectionOutput = 0x00;
static const UInt8 kAudioDeviceSectionGlobal = 0x00;
static const UInt8 kAudioDeviceSectionWildcard = 0xFF;
static const UInt32 kFramesPerPacket = 1;
static const UInt32 kChannelsPerFrame = 2;
static const UInt32 kBitsPerChannel = 16;
static const UInt32 kBytesPerPacket = kChannelsPerFrame * kBitsPerChannel / 8;
static const UInt32 kBytesPerFrame = kBytesPerPacket;
static const UInt32 kFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger;
struct Desc : public AudioStreamBasicDescription
{
Desc( Float64 sampleRate, UInt32 formatID, UInt32 formatFlags, UInt32 bytesPerPacket,
UInt32 framesPerPacket, UInt32 bytesPerFrame, UInt32 channelsPerFrame, UInt32 bitsPerChannel );
};
Desc::Desc( Float64 sampleRate, UInt32 formatID, UInt32 formatFlags, UInt32 bytesPerPacket,
UInt32 framesPerPacket, UInt32 bytesPerFrame, UInt32 channelsPerFrame, UInt32 bitsPerChannel )
{
mSampleRate = sampleRate;
mFormatID = formatID;
mFormatFlags = formatFlags;
mBytesPerPacket = bytesPerPacket;
mFramesPerPacket = framesPerPacket;
mBytesPerFrame = bytesPerFrame;
mChannelsPerFrame = channelsPerFrame;
mBitsPerChannel = bitsPerChannel;
mReserved = 0;
}
/* temporary hack: */
static float g_fLastIOProcTime = 0;
static const int NUM_MIX_TIMES = 16;
static float g_fLastMixTimes[NUM_MIX_TIMES];
static int g_iLastMixTimePos = 0;
static int g_iNumIOProcCalls = 0;
RageSoundDriver_CA::RageSoundDriver_CA() : m_fLatency(0.0f), m_Converter(NULL), m_bStarted(false), m_iSampleRate(0),
m_iLastSampleTime(0), m_iOffset(0), m_pIOThread(NULL), m_pNotificationThread(NULL)
{
}
static inline RString FourCCToString( uint32_t num )
{
RString s( 4, '?' );
char c;
c = (num >> 24) & 0xFF;
if( c >='\x20' && c <= '\x7e' )
s[0] = c;
c = (num >> 16) & 0xFF;
if( c >='\x20' && c <= '\x7e' )
s[1] = c;
c = (num >> 8) & 0xFF;
if( c >='\x20' && c <= '\x7e' )
s[2] = c;
c = num & 0xFF;
if( c >= '\x20' && c <= '\x7e' )
s[3] = c;
return s;
}
#define WERROR(str, num, extra...) str ": '%s' (%lu).", ## extra, FourCCToString(num).c_str(), (num)
#define ERROR(str, num, extra...) (ssprintf(WERROR(str, (num), ## extra)))
RString RageSoundDriver_CA::Init()
{
OSStatus error;
UInt32 size = sizeof(m_OutputDevice);
if( (error = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &m_OutputDevice)) )
return ERROR( "Couldn't create default output device", error );
// Get the HAL's runloop and attach an observer
{
CFRunLoopRef runLoopRef;
CFRunLoopObserverRef observerRef;
CFRunLoopObserverContext context = { 0, this, NULL, NULL, NULL };
size = sizeof(CFRunLoopRef);
if( (error = AudioHardwareGetProperty(kAudioHardwarePropertyRunLoop, &size, &runLoopRef)) )
{
LOG->Warn( WERROR("Couldn't get the HAL's run loop", error) );
}
else
{
observerRef = CFRunLoopObserverCreate( kCFAllocatorDefault, kCFRunLoopEntry,
false, 0, NameHALThread, &context );
CFRunLoopAddObserver( runLoopRef, observerRef, kCFRunLoopDefaultMode );
}
observerRef = CFRunLoopObserverCreate( kCFAllocatorDefault, kCFRunLoopEntry,
false, 0, NameHALThread, &context );
CFRunLoopAddObserver( runLoopRef, observerRef, kCFRunLoopDefaultMode );
}
// Log the name and manufacturer
{
char str[256];
CFStringRef ref;
str[0] = '\0';
size = sizeof(CFStringRef);
if( !AudioDeviceGetProperty(m_OutputDevice, 0, kAudioDeviceSectionGlobal,
kAudioDevicePropertyDeviceNameCFString, &size, &ref) )
{
CFStringGetCString( ref, str, sizeof(str), kCFStringEncodingUTF8 );
CFRelease( ref );
}
if( str[0] == '\0' )
strcpy( str, "(unknown)" );
LOG->Info( "Audio device: %s", str );
str[0] = '\0';
size = sizeof(CFStringRef);
if( !AudioDeviceGetProperty(m_OutputDevice, 0, kAudioDeviceSectionGlobal,
kAudioDevicePropertyDeviceManufacturerCFString, &size, &ref) )
{
CFStringGetCString( ref, str, sizeof(str), kCFStringEncodingUTF8 );
CFRelease( ref );
}
if( str[0] == '\0' )
strcpy( str, "(unknown)" );
LOG->Info( "Audio device manufacturer: %s", str );
}
AddListener( kAudioDeviceProcessorOverload, OverloadListener, "overload" );
AddListener( kAudioDevicePropertyDeviceHasChanged, DeviceChanged, "device changed" );
AddListener( kAudioDevicePropertyJackIsConnected, JackChanged, "jack changed" );
AddListener( kAudioDevicePropertyStreamConfiguration, DeviceChanged, "configuration changed" );
AddListener( kAudioDevicePropertyPreferredChannelsForStereo, DeviceChanged, "preferred channels changed" );
AddListener( kAudioHardwarePropertyDefaultOutputDevice, DeviceChanged, "default output device changed" );
// AddListener( kAudioDevicePropertyStreamFormat, FormatChanged, "stream format changed" );
// Find the stereo channels
UInt32 channels[2];
size = sizeof( channels );
if( (error = AudioDeviceGetProperty(m_OutputDevice, 0, kAudioDeviceSectionOutput,
kAudioDevicePropertyPreferredChannelsForStereo, &size, channels)) )
{
LOG->Warn( WERROR( "Couldn't get the preferred stereo channels", error) );
channels[0] = channels[1] = 0; // Master channel
}
else
{
LOG->Trace( "Preferred stereo channels { %lu, %lu }.", channels[0], channels[1] );
}
UInt32 iFirstChannel = min( channels[0], channels[1] );
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
if( m_iSampleRate == 0 )
m_iSampleRate = 44100;
Float64 nominalSampleRate = m_iSampleRate;
Float64 actualSampleRate;
if( (error = AudioDeviceSetProperty(m_OutputDevice, NULL, iFirstChannel, kAudioDeviceSectionGlobal,
kAudioDevicePropertyNominalSampleRate, sizeof(Float64), &nominalSampleRate)) )
{
LOG->Warn( WERROR("Couldn't set the nominal sample rate", error) );
nominalSampleRate = 0.0;
}
size = sizeof(Float64);
if( (error = AudioDeviceGetProperty(m_OutputDevice, iFirstChannel, kAudioDeviceSectionGlobal,
kAudioDevicePropertyNominalSampleRate, &size, &actualSampleRate)) )
{
return ERROR( "Couldn't get the nominal sample rate", error );
}
if( actualSampleRate == nominalSampleRate )
{
LOG->Info( "Set the nominal sample rate to %f.", nominalSampleRate );
}
else
{
LOG->Warn( "Reported nominal sample rate of %f.", actualSampleRate );
m_iSampleRate = int( actualSampleRate );
}
AudioStreamID *streams, stream;
if( (error = AudioDeviceGetPropertyInfo(m_OutputDevice, iFirstChannel, kAudioDeviceSectionOutput,
kAudioDevicePropertyStreams, &size, NULL)) )
{
return ERROR( "Couldn't get the stream size", error );
}
if( size == 0 )
return "No streams.";
streams = new AudioStreamID[size/sizeof(AudioStreamID)];
if( (error = AudioDeviceGetProperty(m_OutputDevice, iFirstChannel, kAudioDeviceSectionOutput,
kAudioDevicePropertyStreams, &size, streams)) )
{
delete[] streams;
return ERROR( "Couldn't get streams", error );
}
stream = streams[0];
size = sizeof( UInt32 );
for( unsigned i = 0; i < size/sizeof(AudioStreamID); ++i )
{
UInt32 firstChannel;
// I'm not really sure why I'm doing this except it's what the c++ wrapper did, more or less.
if( streams[i] < stream )
stream = streams[i];
// Look for the matching stream.
if( (error = AudioStreamGetProperty(streams[i], 0, kAudioStreamPropertyStartingChannel,
&size, &firstChannel)) )
{
LOG->Warn( WERROR("Failed to get stream starting channel", error) );
continue;
}
if( firstChannel != iFirstChannel )
{
stream = streams[i];
LOG->Trace( "Found a stream with a matching channel." );
break;
}
}
delete[] streams;
// The canonical format
Desc IOProcFormat( actualSampleRate, kAudioFormatLinearPCM, kAudioFormatFlagsNativeFloatPacked, 8, 1, 8, 2, 32 );
const Desc SMFormat( actualSampleRate, kAudioFormatLinearPCM, kFormatFlags, kBytesPerPacket,
kFramesPerPacket, kBytesPerFrame, kChannelsPerFrame, kBitsPerChannel );
if( (error = AudioStreamSetProperty(stream, NULL, 0, kAudioDevicePropertyStreamFormat,
sizeof(Desc), &IOProcFormat)) )
{
LOG->Warn( WERROR("Couldn't set the IOProc format to the canonical format.", error) );
size = sizeof(Desc);
if( (error = AudioStreamGetProperty(stream, 0, kAudioDevicePropertyStreamFormat, &size, &IOProcFormat)) )
return ERROR( "Couldn't get the IOProc format", error );
}
if( (error = AudioConverterNew(&SMFormat, &IOProcFormat, &m_Converter)) )
return ERROR( "Couldn't create the audio converter", error );
// Find which stream of the ones passed to the GetData should be used.
AudioBufferList abl;
size = sizeof(abl);
if( (error = AudioDeviceGetProperty(m_OutputDevice, 0, kAudioDeviceSectionOutput,
kAudioDevicePropertyStreamConfiguration, &size, &abl)) )
{
return ERROR( "Couldn't get the stream configuration", error );
}
m_iBufferNumber = 0;
for( UInt32 i = 0, channelCount = 0; i < abl.mNumberBuffers; ++i )
{
// 0 is the master channel so others start at 1 (I hope) so >= instead of >.
channelCount += abl.mBuffers[i].mNumberChannels;
if( channelCount >= iFirstChannel )
{
m_iBufferNumber = i;
LOG->Trace( "Found the correct buffer number: %lu", i );
break;
}
}
UInt32 bufferSize;
size = sizeof(UInt32);
if( (error = AudioDeviceGetProperty(m_OutputDevice, iFirstChannel, kAudioDeviceSectionOutput,
kAudioDevicePropertyBufferFrameSize, &size, &bufferSize)) )
{
LOG->Warn( WERROR("Couldn't determine buffer size", error) );
bufferSize = 0;
}
else
{
LOG->Trace( "I/O buffer size: %lu frames.", bufferSize );
}
UInt32 frames;
size = sizeof(UInt32);
if( (error = AudioDeviceGetProperty(m_OutputDevice, iFirstChannel, kAudioDeviceSectionOutput,
kAudioDevicePropertyLatency, &size, &frames)) )
{
return ERROR( "Couldn't get device latency", error );
}
bufferSize += frames;
LOG->Trace( "Frames of device latency: %lu", frames );
size = sizeof(UInt32);
if( (error = AudioDeviceGetProperty(m_OutputDevice, iFirstChannel, kAudioDeviceSectionOutput,
kAudioDevicePropertySafetyOffset, &size, &frames)) )
{
return ERROR( "Couldn't get device safety offset", error );
}
bufferSize += frames;
LOG->Trace( "Frames of safety offset: %lu", frames );
size = sizeof(UInt32);
if( (error = AudioStreamGetProperty(stream, 0, kAudioDevicePropertyLatency, &size, &frames)) )
{
LOG->Warn( WERROR("Stream does not report latency", error) );
}
else
{
bufferSize += frames;
LOG->Trace( "Frames of stream latency: %lu", frames );
}
m_fLatency = bufferSize / nominalSampleRate;
LOG->Info( "Frames of latency: %lu\n"
"Seconds of latency: %f", bufferSize, m_fLatency );
StartDecodeThread();
if( (error = AudioDeviceAddIOProc(m_OutputDevice, GetData, this)) )
return ERROR( "Couldn't add the IOProc", error );
if( (error = AudioDeviceStart(m_OutputDevice, GetData)) )
{
AudioDeviceRemoveIOProc( m_OutputDevice, GetData );
return ERROR( "Couldn't start the IOProc", error );
}
m_bStarted = true;
return RString();
}
RageSoundDriver_CA::~RageSoundDriver_CA()
{
if( m_bStarted )
{
AudioDeviceStop( m_OutputDevice, GetData );
AudioDeviceRemoveIOProc( m_OutputDevice, GetData );
RemoveListeners();
delete m_pIOThread;
delete m_pNotificationThread;
}
if( m_Converter != NULL )
AudioConverterDispose( m_Converter );
AudioHardwareUnload();
}
void RageSoundDriver_CA::AddListener( AudioDevicePropertyID propertyID, AudioDevicePropertyListenerProc handler,
const char *name )
{
OSStatus error;
if( (error = AudioDeviceAddPropertyListener(m_OutputDevice, kAudioPropertyWildcardChannel,
kAudioDeviceSectionOutput, propertyID, handler, this)) )
{
LOG->Warn( WERROR("Couldn't install %s listener", error, name) );
}
else
{
m_vPropertyListeners.push_back( pair<AudioDevicePropertyID,
AudioDevicePropertyListenerProc>(propertyID, handler) );
}
}
void RageSoundDriver_CA::RemoveListeners()
{
while( m_vPropertyListeners.size() )
{
pair<AudioHardwarePropertyID, AudioDevicePropertyListenerProc>& p = m_vPropertyListeners.back();
AudioDeviceRemovePropertyListener( m_OutputDevice, kAudioPropertyWildcardChannel,
kAudioDeviceSectionOutput, p.first, p.second );
m_vPropertyListeners.pop_back();
}
}
int64_t RageSoundDriver_CA::GetPosition() const
{
AudioTimeStamp time;
OSStatus error;
if( (error = AudioDeviceGetCurrentTime(m_OutputDevice, &time)) )
{
if( error != kAudioHardwareNotRunningError )
FAIL_M( ERROR("GetCurrentTime() failed", error) );
return m_iLastSampleTime;
}
m_iLastSampleTime = int64_t( time.mSampleTime ) + m_iOffset;
return m_iLastSampleTime;
}
void RageSoundDriver_CA::NameHALThread( CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info )
{
RageSoundDriver_CA *This = (RageSoundDriver_CA *)info;
This->m_pNotificationThread = new RageThreadRegister( "HAL notification thread" );
// Remove and release the observer.
CFRunLoopObserverInvalidate( observer );
CFRelease( observer );
}
OSStatus RageSoundDriver_CA::GetData( AudioDeviceID inDevice,
const AudioTimeStamp *inNow,
const AudioBufferList *inInputData,
const AudioTimeStamp *inInputTime,
AudioBufferList *outOutputData,
const AudioTimeStamp *inOutputTime,
void *inClientData )
{
RageTimer tm;
RageSoundDriver_CA *This = (RageSoundDriver_CA *)inClientData;
if( unlikely(This->m_pIOThread == NULL) )
This->m_pIOThread = new RageThreadRegister( "HAL I/O thread" );
AudioBuffer& buf = outOutputData->mBuffers[This->m_iBufferNumber];
UInt32 dataPackets = buf.mDataByteSize >> 3; // 8 byes per packet
int64_t decodePos = int64_t( inOutputTime->mSampleTime ) + This->m_iOffset;
int64_t now = int64_t( inNow->mSampleTime ) + This->m_iOffset;
This->m_iLastSampleTime = now;
RageTimer tm2;
int16_t buffer[dataPackets * (kBytesPerPacket >> 1)];
This->Mix( buffer, dataPackets, decodePos, now) ;
g_fLastMixTimes[g_iLastMixTimePos] = tm2.GetDeltaTime();
++g_iLastMixTimePos;
wrap( g_iLastMixTimePos, NUM_MIX_TIMES );
AudioConverterConvertBuffer( This->m_Converter, dataPackets * kBytesPerPacket,
buffer, &buf.mDataByteSize, buf.mData );
// Zero other buffers. Not sure if this is needed, but I suspect it is.
for( unsigned i = 0; i < outOutputData->mNumberBuffers; ++i )
{
if( i == This->m_iBufferNumber )
continue;
memset( outOutputData->mBuffers[i].mData, 0, outOutputData->mBuffers[i].mDataByteSize );
}
g_fLastIOProcTime = tm.GetDeltaTime();
++g_iNumIOProcCalls;
return noErr;
}
OSStatus RageSoundDriver_CA::OverloadListener( AudioDeviceID inDevice,
UInt32 inChannel,
Boolean isInput,
AudioDevicePropertyID inPropertyID,
void *inData )
{
if( isInput )
return noErr;
RString Output;
for( int i = NUM_MIX_TIMES-1; i >= 0; --i )
{
int pos = (g_iLastMixTimePos+i) % NUM_MIX_TIMES;
Output += ssprintf( "%.3f ", g_fLastMixTimes[pos] );
}
LOG->Warn( "Audio overload. Last IOProc time: %f IOProc calls: %i (%s)",
g_fLastIOProcTime, g_iNumIOProcCalls, Output.c_str() );
g_iNumIOProcCalls = 0;
return noErr;
}
OSStatus RageSoundDriver_CA::DeviceChanged( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput,
AudioDevicePropertyID inPropertyID, void *inData )
{
if( isInput )
return noErr;
LOG->Trace( "Device configuration changed." );
return noErr;
}
OSStatus RageSoundDriver_CA::JackChanged( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput,
AudioDevicePropertyID inPropertyID, void *inData )
{
if( isInput )
return noErr;
RageSoundDriver_CA *This = (RageSoundDriver_CA *)inData;
UInt32 result;
UInt32 size = sizeof( result );
AudioTimeStamp time;
OSStatus error;
AudioDeviceGetProperty( inDevice, inChannel, 0, inPropertyID, &size, &result );
CHECKPOINT_M( ssprintf("Channel %lu's has %s plugged into its jack.", inChannel,
result ? "something" : "nothing") );
if( (error = AudioDeviceGetCurrentTime(inDevice, &time)) )
{
if( error != kAudioHardwareNotRunningError )
FAIL_M( ERROR("Couldn't get current time when jack changed", error) );
This->m_iOffset = This->m_iLastSampleTime;
}
else
{
This->m_iOffset = This->m_iLastSampleTime - int64_t( time.mSampleTime );
}
return noErr;
}
void RageSoundDriver_CA::SetupDecodingThread()
{
/* Increase the scheduling precedence of the decoder thread. */
SetThreadPrecedence( 0.75f );
}
/*
* (c) 2004-2006 Steve Checkoway
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
@@ -1,80 +0,0 @@
#ifndef RAGE_SOUND_DRIVER_CA_H
#define RAGE_SOUND_DRIVER_CA_H
#include "RageSoundDriver_Generic_Software.h"
#include <vector>
#include <CoreFoundation/CoreFoundation.h>
#include <AudioToolbox/AudioToolbox.h>
class RageSoundBase;
class RageThreadRegister;
class RageSoundDriver_CA : public RageSound_Generic_Software
{
public:
RageSoundDriver_CA();
RString Init();
~RageSoundDriver_CA();
float GetPlayLatency() const { return m_fLatency; }
int GetSampleRate() const { return m_iSampleRate; }
int64_t GetPosition() const;
void SetupDecodingThread();
private:
float m_fLatency;
AudioDeviceID m_OutputDevice;
AudioConverterRef m_Converter;
bool m_bStarted;
int m_iSampleRate;
mutable int64_t m_iLastSampleTime;
int64_t m_iOffset;
UInt32 m_iBufferNumber;
vector<pair<AudioDevicePropertyID, AudioDevicePropertyListenerProc> > m_vPropertyListeners;
RageThreadRegister *m_pIOThread, *m_pNotificationThread;
void AddListener( AudioDevicePropertyID propertyID,
AudioDevicePropertyListenerProc handler, const char *name );
void RemoveListeners();
static OSStatus GetData( AudioDeviceID inDevice,
const AudioTimeStamp *inNow,
const AudioBufferList *inInputData,
const AudioTimeStamp *inInputTime,
AudioBufferList *outOutputData,
const AudioTimeStamp *inOutputTime,
void *inClientData );
static void NameHALThread( CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info );
static OSStatus OverloadListener( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput,
AudioDevicePropertyID inPropertyID, void *inData );
static OSStatus DeviceChanged( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput,
AudioDevicePropertyID inPropertyID, void *inData );
static OSStatus JackChanged( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput,
AudioDevicePropertyID inPropertyID, void *inData );
};
#endif
/*
* (c) 2004-2006 Steve Checkoway
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/