finished implementation of different audio compression types: IMA-ADPCM (already available), MS-ADPCM (new), None (new)

This commit is contained in:
Volker Fischer 2008-08-10 08:14:30 +00:00
parent e727287054
commit c1e706e21f
26 changed files with 264 additions and 108 deletions

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer, Erik de Castro Lopo
@ -134,7 +134,12 @@ protected:
class CAudioCompression
{
public:
enum EAudComprType { CT_NONE, CT_IMAADPCM, CT_MSADPCM };
enum EAudComprType
{
CT_NONE = 0,
CT_IMAADPCM = 1,
CT_MSADPCM = 2
};
CAudioCompression() {}
virtual ~CAudioCompression() {}
@ -144,6 +149,8 @@ public:
CVector<unsigned char> Encode ( const CVector<short>& vecsAudio );
CVector<short> Decode ( const CVector<unsigned char>& vecbyAdpcm );
EAudComprType GetType() { return eAudComprType; }
protected:
EAudComprType eAudComprType;
CImaAdpcm ImaAdpcm;

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer
@ -95,5 +95,4 @@ protected:
int iPutPos;
};
#endif /* !defined ( BUFFER_H__3B123453_4344_BB23945IUHF1912__INCLUDED_ ) */

View file

@ -30,10 +30,11 @@
\******************************************************************************/
CChannelSet::CChannelSet() : bWriteStatusHTMLFile ( false )
{
// enable all channels
// enable all channels and set server flag
for ( int i = 0; i < MAX_NUM_CHANNELS; i++ )
{
vecChannels[i].SetEnable ( true );
vecChannels[i].SetIsServer ( true );
}
// CODE TAG: MAX_NUM_CHANNELS_TAG
@ -512,7 +513,7 @@ void CChannelSet::WriteHTMLChannelList()
* CChannel *
\******************************************************************************/
CChannel::CChannel() : sName ( "" ),
vecdGains ( MAX_NUM_CHANNELS, (double) 1.0 ),
vecdGains ( MAX_NUM_CHANNELS, (double) 1.0 ), bIsServer ( false ),
// it is important to give the following parameters meaningful initial
// values because they are dependend on each other
iCurSockBufSize ( DEF_NET_BUF_SIZE_NUM_BL ),
@ -521,15 +522,27 @@ CChannel::CChannel() : sName ( "" ),
// query all possible network in buffer sizes for determining if an
// audio packet was received (the following code only works if all
// possible network buffer sizes are different!)
vecNetwBufferInProps.Init ( 2 * MAX_NET_BLOCK_SIZE_FACTOR );
const int iNumSupportedAudComprTypes = 3;
vecNetwBufferInProps.Init ( iNumSupportedAudComprTypes *
MAX_NET_BLOCK_SIZE_FACTOR );
for ( int i = 0; i < MAX_NET_BLOCK_SIZE_FACTOR; i++ )
{
const int iIMAIdx = 2 * i;
const int iMSIdx = 2 * i + 1;
const int iNoneIdx = iNumSupportedAudComprTypes * i;
const int iIMAIdx = iNumSupportedAudComprTypes * i + 1;
const int iMSIdx = iNumSupportedAudComprTypes * i + 2;
// network block size factor must start from 1 -> i + 1
vecNetwBufferInProps[iIMAIdx].iBlockSizeFactor = i + 1;
vecNetwBufferInProps[iMSIdx].iBlockSizeFactor = i + 1;
const int iCurNetBlockSizeFact = i + 1;
vecNetwBufferInProps[iNoneIdx].iBlockSizeFactor = iCurNetBlockSizeFact;
vecNetwBufferInProps[iIMAIdx].iBlockSizeFactor = iCurNetBlockSizeFact;
vecNetwBufferInProps[iMSIdx].iBlockSizeFactor = iCurNetBlockSizeFact;
// None (no audio compression)
vecNetwBufferInProps[iNoneIdx].eAudComprType = CAudioCompression::CT_NONE;
vecNetwBufferInProps[iNoneIdx].iNetwInBufSize = AudioCompressionIn.Init (
vecNetwBufferInProps[iNoneIdx].iBlockSizeFactor * MIN_BLOCK_SIZE_SAMPLES,
vecNetwBufferInProps[iNoneIdx].eAudComprType );
// IMA ADPCM
vecNetwBufferInProps[iIMAIdx].eAudComprType = CAudioCompression::CT_IMAADPCM;
@ -548,9 +561,12 @@ CChannel::CChannel() : sName ( "" ),
SetSockBufSize ( DEF_NET_BUF_SIZE_NUM_BL );
// set initial input and output block size factors
SetNetwInBlSiFactAndCompr ( DEF_NET_BLOCK_SIZE_FACTOR, CAudioCompression::CT_IMAADPCM );
SetNetwInBlSiFactAndCompr ( DEF_NET_BLOCK_SIZE_FACTOR, CAudioCompression::CT_MSADPCM );
SetNetwBufSizeFactOut ( DEF_NET_BLOCK_SIZE_FACTOR );
// set initial audio compression format for output
SetAudioCompressionOut ( CAudioCompression::CT_MSADPCM );
// init time-out for the buffer with zero -> no connection
iConTimeOut = 0;
@ -624,18 +640,32 @@ void CChannel::SetNetwInBlSiFactAndCompr ( const int iNewBlockSizeFactor,
}
void CChannel::SetNetwBufSizeFactOut ( const int iNewNetwBlSiFactOut )
{
Mutex.lock();
{
// store new value
iCurNetwOutBlSiFact = iNewNetwBlSiFactOut;
// init audio compression unit
// init audio compression and get audio compression block size
iAudComprSizeOut = AudioCompressionOut.Init (
iNewNetwBlSiFactOut * MIN_BLOCK_SIZE_SAMPLES,
CAudioCompression::CT_IMAADPCM );
iNewNetwBlSiFactOut * MIN_BLOCK_SIZE_SAMPLES, eAudComprTypeOut );
// init conversion buffer
ConvBuf.Init ( iNewNetwBlSiFactOut * MIN_BLOCK_SIZE_SAMPLES );
}
Mutex.unlock();
}
void CChannel::SetAudioCompressionOut ( const CAudioCompression::EAudComprType eNewAudComprTypeOut )
{
// store new value
eAudComprTypeOut = eNewAudComprTypeOut;
// call "set network buffer size factor" function because its initialization
// depends on the audio compression format and implicitely, the audio compression
// is initialized
SetNetwBufSizeFactOut ( iCurNetwOutBlSiFact );
}
void CChannel::OnSendProtMessage ( CVector<uint8_t> vecMessage )
{
@ -673,17 +703,11 @@ void CChannel::SetSockBufSize ( const int iNumBlocks )
void CChannel::OnNetwBlSiFactChange ( int iNewNetwBlSiFact )
{
// TEST
//qDebug ( "new network block size factor: %d", iNewNetwBlSiFact );
SetNetwBufSizeFactOut ( iNewNetwBlSiFact );
}
void CChannel::OnJittBufSizeChange ( int iNewJitBufSize )
{
// TEST
//qDebug ( "new jitter buffer size: %d", iNewJitBufSize );
SetSockBufSize ( iNewJitBufSize );
}
@ -766,11 +790,25 @@ EPutDataStat CChannel::PutData ( const CVector<unsigned char>& vecbyData,
const int iNewNetwInBlSiFact =
vecNetwBufferInProps[i].iBlockSizeFactor;
if ( iNewNetwInBlSiFact != iCurNetwInBlSiFact )
const CAudioCompression::EAudComprType eNewAudComprType =
vecNetwBufferInProps[i].eAudComprType;
if ( ( iNewNetwInBlSiFact != iCurNetwInBlSiFact ) ||
( eNewAudComprType != AudioCompressionIn.GetType() ) )
{
// re-initialize to new value
SetNetwInBlSiFactAndCompr ( iNewNetwInBlSiFact,
vecNetwBufferInProps[i].eAudComprType );
eNewAudComprType );
}
// in case of a server channel, use the same audio
// compression for output as for the input
if ( bIsServer )
{
if ( GetAudioCompressionOut() != vecNetwBufferInProps[i].eAudComprType )
{
SetAudioCompressionOut ( vecNetwBufferInProps[i].eAudComprType );
}
}
}
}
@ -783,22 +821,14 @@ EPutDataStat CChannel::PutData ( const CVector<unsigned char>& vecbyData,
// decompress audio
CVector<short> vecsDecomprAudio ( AudioCompressionIn.Decode ( vecbyData ) );
// do resampling to compensate for sample rate offsets in the
// different sound cards of the clients
/*
for (int i = 0; i < BLOCK_SIZE_SAMPLES; i++)
vecdResInData[i] = (double) vecsData[i];
const int iInSize = ResampleObj.Resample(vecdResInData, vecdResOutData,
(double) SYSTEM_SAMPLE_RATE / (SYSTEM_SAMPLE_RATE - dSamRateOffset));
*/
vecdResOutData.Init ( iCurNetwInBlSiFact * MIN_BLOCK_SIZE_SAMPLES );
for ( int i = 0; i < iCurNetwInBlSiFact * MIN_BLOCK_SIZE_SAMPLES; i++ ) {
vecdResOutData[i] = (double) vecsDecomprAudio[i];
// convert received data from short to double
CVector<double> vecdDecomprAudio ( iCurNetwInBlSiFact * MIN_BLOCK_SIZE_SAMPLES );
for ( int i = 0; i < iCurNetwInBlSiFact * MIN_BLOCK_SIZE_SAMPLES; i++ )
{
vecdDecomprAudio[i] = static_cast<double> ( vecsDecomprAudio[i] );
}
if ( SockBuf.Put ( vecdResOutData ) )
if ( SockBuf.Put ( vecdDecomprAudio ) )
{
eRet = PS_AUDIO_OK;
}
@ -883,6 +913,8 @@ CVector<unsigned char> CChannel::PrepSendPacket ( const CVector<short>& vecsNPac
// tell the following network send routine that nothing should be sent
CVector<unsigned char> vecbySendBuf ( 0 );
Mutex.lock(); // get mutex lock
{
// use conversion buffer to convert sound card block size in network
// block size
if ( ConvBuf.Put ( vecsNPacket ) )
@ -891,6 +923,8 @@ CVector<unsigned char> CChannel::PrepSendPacket ( const CVector<short>& vecsNPac
vecbySendBuf.Init ( iAudComprSizeOut );
vecbySendBuf = AudioCompressionOut.Encode ( ConvBuf.Get() );
}
}
Mutex.unlock(); // get mutex unlock
return vecbySendBuf;
}

View file

@ -83,7 +83,7 @@ public:
bool IsConnected() const { return iConTimeOut > 0; }
void SetEnable ( const bool bNEnStat );
bool IsEnabled() { return bIsEnabled; }
void SetIsServer ( const bool bNEnStat ) { bIsServer = bNEnStat; }
void SetAddress ( const CHostAddress NAddr ) { InetAddr = NAddr; }
bool GetAddress ( CHostAddress& RetAddr );
@ -109,6 +109,9 @@ public:
int GetNetwBufSizeFactIn() { return iCurNetwInBlSiFact; }
void SetAudioCompressionOut ( const CAudioCompression::EAudComprType eNewAudComprTypeOut );
CAudioCompression::EAudComprType GetAudioCompressionOut() { return eAudComprTypeOut; }
// network protocol interface
void CreateJitBufMes ( const int iJitBufSize )
{
@ -144,12 +147,6 @@ protected:
CAudioCompression AudioCompressionOut;
int iAudComprSizeOut;
// resampling
CResample ResampleObj;
double dSamRateOffset;
CVector<double> vecdResInData;
CVector<double> vecdResOutData;
// connection parameters
CHostAddress InetAddr;
@ -173,6 +170,12 @@ protected:
int iConTimeOutStartVal;
bool bIsEnabled;
bool bIsServer;
int iCurNetwInBlSiFact;
int iCurNetwOutBlSiFact;
QMutex Mutex;
struct sNetwBufferInProps
{
@ -182,10 +185,7 @@ protected:
};
CVector<sNetwBufferInProps> vecNetwBufferInProps;
int iCurNetwInBlSiFact;
int iCurNetwOutBlSiFact;
QMutex Mutex;
CAudioCompression::EAudComprType eAudComprTypeOut;
public slots:
void OnSendProtMessage ( CVector<uint8_t> vecMessage );

View file

@ -35,6 +35,7 @@
#include "socket.h"
#include "resample.h"
#include "channel.h"
#include "audiocompr.h"
#include "util.h"
#ifdef _WIN32
# include "../windows/sound.h"
@ -110,12 +111,13 @@ public:
int GetNetwBufSizeFactIn() { return iNetwBufSizeFactIn; }
void SetNetwBufSizeFactOut ( const int iNetNetwBlSiFact )
{
// set the new socket size
Channel.SetNetwBufSizeFactOut ( iNetNetwBlSiFact );
}
{ Channel.SetNetwBufSizeFactOut ( iNetNetwBlSiFact ); }
int GetNetwBufSizeFactOut() { return Channel.GetNetwBufSizeFactOut(); }
void SetAudioCompressionOut ( const CAudioCompression::EAudComprType eNewAudComprTypeOut )
{ Channel.SetAudioCompressionOut ( eNewAudComprTypeOut ); }
CAudioCompression::EAudComprType GetAudioCompressionOut() { return Channel.GetAudioCompressionOut(); }
void SetRemoteChanGain ( const int iId, const double dGain )
{ Channel.SetRemoteChanGain ( iId, dGain ); }
@ -193,5 +195,4 @@ signals:
void PingTimeReceived ( int iPingTime );
};
#endif /* !defined ( CLIENT_HOIHGE76GEKJH98_3_43445KJIUHF1912__INCLUDED_ ) */

View file

@ -92,6 +92,25 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, QWidget* parent,
cbOpenChatOnNewMessage->setCheckState ( Qt::Unchecked );
}
// audio compression type
switch ( pClient->GetAudioCompressionOut() )
{
case CAudioCompression::CT_NONE:
radioButtonNoAudioCompr->setChecked ( true );
break;
case CAudioCompression::CT_IMAADPCM:
radioButtonIMA_ADPCM->setChecked ( true );
break;
case CAudioCompression::CT_MSADPCM:
radioButtonMS_ADPCM->setChecked ( true );
break;
}
AudioCompressionButtonGroup.addButton ( radioButtonNoAudioCompr );
AudioCompressionButtonGroup.addButton ( radioButtonIMA_ADPCM );
AudioCompressionButtonGroup.addButton ( radioButtonMS_ADPCM );
// Connections -------------------------------------------------------------
// timers
@ -123,6 +142,9 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, QWidget* parent,
QObject::connect ( pClient, SIGNAL ( PingTimeReceived ( int ) ),
this, SLOT ( OnPingTimeResult ( int ) ) );
QObject::connect ( &AudioCompressionButtonGroup, SIGNAL ( buttonClicked ( QAbstractButton* ) ),
this, SLOT ( OnAudioCompressionButtonGroupClicked ( QAbstractButton* ) ) );
// Timers ------------------------------------------------------------------
// start timer for status bar
@ -186,6 +208,25 @@ void CClientSettingsDlg::OnOpenChatOnNewMessageStateChanged ( int value )
UpdateDisplay();
}
void CClientSettingsDlg::OnAudioCompressionButtonGroupClicked ( QAbstractButton* button )
{
if ( button == radioButtonNoAudioCompr )
{
pClient->SetAudioCompressionOut ( CAudioCompression::CT_NONE );
}
if ( button == radioButtonIMA_ADPCM )
{
pClient->SetAudioCompressionOut ( CAudioCompression::CT_IMAADPCM );
}
if ( button == radioButtonMS_ADPCM )
{
pClient->SetAudioCompressionOut ( CAudioCompression::CT_MSADPCM );
}
UpdateDisplay();
}
void CClientSettingsDlg::OnTimerPing()
{
// send ping message to server

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer
@ -33,8 +33,10 @@
#include <qradiobutton.h>
#include <qmenubar.h>
#include <qlayout.h>
#include <qbuttongroup.h>
#include "global.h"
#include "client.h"
#include "audiocompr.h"
#include "multicolorled.h"
#ifdef _WIN32
# include "../windows/moc/clientsettingsdlgbase.h"
@ -64,6 +66,7 @@ protected:
CClient* pClient;
QTimer TimerStatus;
QTimer TimerPing;
QButtonGroup AudioCompressionButtonGroup;
void UpdateDisplay();
virtual void showEvent ( QShowEvent* showEvent );
@ -78,5 +81,6 @@ public slots:
void OnSliderNetBufSiFactIn ( int value );
void OnSliderNetBufSiFactOut ( int value );
void OnOpenChatOnNewMessageStateChanged ( int value );
void OnAudioCompressionButtonGroupClicked ( QAbstractButton* button );
void OnPingTimeResult ( int iPingTime );
};

View file

@ -6,7 +6,7 @@
<x>0</x>
<y>0</y>
<width>443</width>
<height>244</height>
<height>260</height>
</rect>
</property>
<property name="windowTitle" >
@ -724,16 +724,22 @@
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="QCheckBox" name="cbOpenChatOnNewMessage" >
<property name="text" >
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>Open chat on new message</string>
</property>
@ -741,6 +747,37 @@
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBoxAudioCompressionType" >
<property name="title" >
<string>Audio Compression</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QRadioButton" name="radioButtonIMA_ADPCM" >
<property name="text" >
<string>IMA ADPCM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButtonMS_ADPCM" >
<property name="text" >
<string>MS ADPCM</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButtonNoAudioCompr" >
<property name="text" >
<string>None</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
@ -750,8 +787,8 @@
</property>
<property name="sizeHint" >
<size>
<width>98</width>
<height>21</height>
<width>116</width>
<height>20</height>
</size>
</property>
</spacer>

View file

@ -183,5 +183,4 @@ bool GetNumericArgument ( int argc, char **argv, int &i, std::string strS
void PostWinMessage ( const _MESSAGE_IDENT MessID, const int iMessageParam = 0,
const int iChanNum = 0 );
#endif /* !defined ( GLOBAL_H__3B123453_4344_BB2B_23E7A0D31912__INCLUDED_ ) */

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -175,5 +175,4 @@ signals:
void ServerFull();
};
#endif /* !defined ( PROTOCOL_H__3B123453_4344_BB2392354455IUHF1912__INCLUDED_ ) */

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer
@ -88,5 +88,4 @@ public slots:
void OnSendProtMessage ( int iChID, CVector<uint8_t> vecMessage );
};
#endif /* !defined ( SERVER_HOIHGE7LOKIH83JH8_3_43445KJIUHF1912__INCLUDED_ ) */

View file

@ -112,6 +112,26 @@ void CSettings::ReadIniFile ( const QString& sFileName )
{
pClient->SetOpenChatOnNewMessage ( bValue );
}
// audio compression type (check CAudioCompression::EAudComprType definition
// for integer numbers!)
if ( GetNumericIniSet ( IniXMLDocument, "client", "audiocompression", 0, 2, iValue ) )
{
switch ( iValue )
{
case 0:
pClient->SetAudioCompressionOut ( CAudioCompression::CT_NONE );
break;
case 1:
pClient->SetAudioCompressionOut ( CAudioCompression::CT_IMAADPCM );
break;
case 2:
break;
pClient->SetAudioCompressionOut ( CAudioCompression::CT_MSADPCM );
}
}
}
void CSettings::WriteIniFile ( const QString& sFileName )
@ -154,6 +174,22 @@ void CSettings::WriteIniFile ( const QString& sFileName )
// flag whether the chat window shall be opened on a new chat message
SetFlagIniSet ( IniXMLDocument, "client", "openchatonnewmessage", pClient->GetOpenChatOnNewMessage() );
// audio compression type (check CAudioCompression::EAudComprType definition
// for integer numbers!)
switch ( pClient->GetAudioCompressionOut() )
{
case CAudioCompression::CT_NONE:
SetNumericIniSet ( IniXMLDocument, "client", "audiocompression", 0 );
break;
case CAudioCompression::CT_IMAADPCM:
SetNumericIniSet ( IniXMLDocument, "client", "audiocompression", 1 );
break;
case CAudioCompression::CT_MSADPCM:
SetNumericIniSet ( IniXMLDocument, "client", "audiocompression", 2 );
break;
}
// prepare file name for storing initialization data in XML file
QString sCurFileName = sFileName;

View file

@ -30,6 +30,7 @@
#include <qtextstream.h>
#include "global.h"
#include "client.h"
#include "audiocompr.h"
/* Definitions ****************************************************************/

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer
@ -74,5 +74,4 @@ public slots:
void OnDataReceived();
};
#endif /* !defined ( SOCKET_HOIHGE76GEKJH98_3_4344_BB23945IUHF1912__INCLUDED_ ) */

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer

View file

@ -1,5 +1,5 @@
rem/******************************************************************************\
rem * Copyright (c) 2004-2006
rem * Copyright (c) 2004-2008
rem *
rem * Author(s):
rem * Volker Fischer

View file

@ -1,5 +1,5 @@
/******************************************************************************\
* Copyright (c) 2004-2006
* Copyright (c) 2004-2008
*
* Author(s):
* Volker Fischer