Commit 8c33c69d authored by Anne Heimes's avatar Anne Heimes

Merge branch 'ba_2016_heimes' of https://git.rwth-aachen.de/ita/ITADataSources into ba_2016_heimes

parents b28e2b15 e09426c6
......@@ -111,6 +111,7 @@ endif( )
if( ITA_DATA_SOURCES_WITH_NET_AUDIO )
list( APPEND ITADataSourcesHeader
"include/ITANetAudioStream.h"
"include/ITANetAudioSampleServer.h"
"include/ITANetAudioStreamingServer.h"
)
list( APPEND ITADataSourcesSources
......
/*
* ----------------------------------------------------------------
*
* ITA core libs
* (c) Copyright Institute of Technical Acoustics (ITA)
* RWTH Aachen University, Germany, 2015-2017
*
* ----------------------------------------------------------------
* ____ __________ _______
* // / //__ ___/ // _ |
* // / // / // /_| |
* // / // / // ___ |
* //__/ //__/ //__/ |__|
*
* ----------------------------------------------------------------
*
*/
#ifndef INCLUDE_WATCHER_ITA_NET_AUDIO_SAMPLE_SERVER
#define INCLUDE_WATCHER_ITA_NET_AUDIO_SAMPLE_SERVER
#include <ITADataSourcesDefinitions.h>
#include <ITANetAudioStreamingServer.h>
#include <ITADataSourceRealization.h>
//! Sample-generation class with abstract method for providing samples
/*
* This ready-to-use class helps to provide samples for a NetAudio streaming server with
* a single method for processing that has to be implemented ...
* ... just derive and implement Process() method. Have a look at Zero() method
* for exemplary usage of sample buffer.
*/
class ITA_DATA_SOURCES_API CITASampleProcessor : public ITADatasourceRealization
{
public:
//! Create a sample processor with streaming parameters
/*
* @param[in] iNumChannels Channels provided
* @param[in] dSampleRate Audio processing sampling rate
* @param[in] iBlockLength Audio processing block length / buffer size
*/
inline CITASampleProcessor( const int iNumChannels, const double dSampleRate, const int iBlockLength )
: ITADatasourceRealization( ( unsigned int ) ( iNumChannels ), dSampleRate, ( unsigned int ) ( iBlockLength ) )
{
m_vvfSampleBuffer.resize( iNumChannels );
for( size_t c = 0; c < iNumChannels; c++ )
m_vvfSampleBuffer[ c ].resize( iBlockLength );
Zero();
};
//! Sets all channels and samples to zero
inline void Zero()
{
/*
* Use this as an example how to work with the buffer structure.
*/
// Iterate over channels
for( size_t c = 0; c < m_vvfSampleBuffer.size(); c++ )
{
std::vector< float >& vfSingleChannelSampleBuffer( m_vvfSampleBuffer[ c ] ); // One channel
// Iterate over samples of channel
for( size_t n = 0; n < vfSingleChannelSampleBuffer.size(); n++ )
{
float& fSample( vfSingleChannelSampleBuffer[ n ] ); // One sample
fSample = 0.0f; // -> Manipulation
}
}
};
//! Process samples (overwrite this virtual method)
/**
* Method that is called in audio streaming context and requests
* to produce or copy audio samples into the internal buffer m_vvfSampleBuffer
*
* @param[in] pStreamInfo Information over streaming status, i.e. sample count and time stamp
*
*/
virtual void Process( const ITAStreamInfo* pStreamInfo ) =0;
protected:
std::vector< std::vector< float > > m_vvfSampleBuffer; //!< Multi-channel sample buffer to be filled
private:
//! Delegate internal buffer to audio stream (ITADatasource)
inline void ProcessStream( const ITAStreamInfo* pInfo )
{
Process( pInfo );
for( size_t c = 0; c < m_vvfSampleBuffer.size(); c++ )
{
float* pfData = GetWritePointer( ( unsigned int ) ( c ) );
for( size_t n = 0; n < m_vvfSampleBuffer[ c ].size(); n++ )
pfData[ n ] = m_vvfSampleBuffer[ c ][ n ];
}
IncrementWritePointer();
};
};
//! Network audio sample server (for providing samples via derived generator class)
/**
* Audio sample transmitter for a networked sample callback function that can connect via TCP/IP.
*
* @sa CITANetAudioStream CITANetAudioStreamingServer CITASampleProcessor
* @note not thread-safe
*/
class ITA_DATA_SOURCES_API CITANetAudioSampleServer : public CITANetAudioStreamingServer
{
public:
inline CITANetAudioSampleServer( CITASampleProcessor* pProcessor )
: m_pSampleProcessor( pProcessor )
{
SetInputStream( m_pSampleProcessor );
};
inline ~CITANetAudioSampleServer()
{};
private:
//! Prohibit public access to streaming context and delegate
inline void SetInputStream( ITADatasource* pDataSource )
{
CITANetAudioStreamingServer::SetInputStream( pDataSource );
};
//! Prohibit public access to streaming context and delegate
inline ITADatasource* GetInputStream() const
{
return CITANetAudioStreamingServer::GetInputStream();
};
CITASampleProcessor* m_pSampleProcessor; //!< Callback / sample processor
};
#endif // INCLUDE_WATCHER_ITA_NET_AUDIO_SAMPLE_SERVER
......@@ -44,7 +44,7 @@ class VistaConnectionIP;
/**
* Audio sample transmitter for a networked signal source that can connect via TCP/IP.
*
* @sa CITANetAudioStream, CITANetAudioSampleServer
* @sa CITANetAudioStream
* @note not thread-safe
*/
class ITA_DATA_SOURCES_API CITANetAudioStreamingServer : public VistaThreadLoop
......@@ -115,6 +115,9 @@ private:
int m_iSendingBlockLength;
int m_iMaxSendBlocks;
double m_dStreamTimeStart; //!< Stream time start
long unsigned int m_nStreamSampleCounts; //!< Samples that has been streamed
friend class CITANetAudioServer;
};
......
// $Id: $
/*
* ----------------------------------------------------------------
*
* ITA core libs
* (c) Copyright Institute of Technical Acoustics (ITA)
* RWTH Aachen University, Germany, 2015-2017
*
* ----------------------------------------------------------------
* ____ __________ _______
* // / //__ ___/ // _ |
* // / // / // /_| |
* // / // / // ___ |
* //__/ //__/ //__/ |__|
*
* ----------------------------------------------------------------
*
*/
#ifndef __ITA_STREAM_INFO_H__
#define __ITA_STREAM_INFO_H__
#ifndef INCLUDE_WATCHER_ITA_STREAM_INFO
#define INCLUDE_WATCHER_ITA_STREAM_INFO
#include <ITATypes.h>
// Diese Datenklasse beschreibt den Zustand eines Audiostreams
class ITAStreamInfo {
//! Time code information for audio streams
class ITAStreamInfo
{
public:
// Anzahl der abgespielten Samples seit Beginn des Streamings
uint64_t nSamples;
uint64_t nSamples; //!< Number of samples processed
double dTimecode; //!< Time code
// TODO: Beschreiben
double dTimecode;
inline ITAStreamInfo()
: nSamples( 0 )
, dTimecode( 0 )
{};
//! Standard-Konstruktor (setzt alle Werte 0)
ITAStreamInfo() : nSamples(0), dTimecode(0) {}
//! Destruktor
virtual ~ITAStreamInfo() {};
};
#endif // __ITA_STREAM_INFO_H__
#endif // INCLUDE_WATCHER_ITA_STREAM_INFO
#include "ITADataSourceRealization.h"
#include <cassert>
#include <ITAFastMath.h>
#include <cassert>
/*
ITADatasourceRealization::ITADatasourceRealization(unsigned int uiChannels,
unsigned int uiBlocklength,
unsigned int uiCapacity)
{
Init(uiChannels, uiBlocklength, uiCapacity);
}
*/
ITADatasourceRealization::ITADatasourceRealization(unsigned int uiChannels,
double dSamplerate,
unsigned int uiBlocklength,
unsigned int uiCapacity)
ITADatasourceRealization::ITADatasourceRealization( unsigned int uiChannels, double dSamplerate, unsigned int uiBlocklength, unsigned int uiCapacity )
{
assert( dSamplerate > 0 );
m_dSampleRate = dSamplerate;
m_oStreamProps.dSamplerate = dSamplerate;
Init(uiChannels, uiBlocklength, uiCapacity);
}
Init( uiChannels, uiBlocklength, uiCapacity );
}
void ITADatasourceRealization::Init(unsigned int uiChannels,
unsigned int uiBlocklength,
unsigned int uiCapacity)
void ITADatasourceRealization::Init( unsigned int uiChannels, unsigned int uiBlocklength, unsigned int uiCapacity )
{
assert( uiChannels > 0 );
assert( uiBlocklength > 0 );
......@@ -49,7 +33,7 @@ void ITADatasourceRealization::Init(unsigned int uiChannels,
m_oStreamProps.uiChannels = m_uiChannels;
m_oStreamProps.uiBlocklength = m_uiBlocklength;
m_uiBufferSize = uiBlocklength * (uiCapacity+1);
m_uiBufferSize = uiBlocklength * ( uiCapacity + 1 );
m_pEventHandler = NULL;
......@@ -57,31 +41,33 @@ void ITADatasourceRealization::Init(unsigned int uiChannels,
Organisation des Puffers: Damit die Blcke der einzelnen Kanle
im Speicher ortlich nher liegen ist das Array wiefolgt indiziert:
[1. Block Kanal 1], ..., [1. Block Kanal k], [2. Block Kanal 1], ...
[1. Block Kanal 1], ..., [1. Block Kanal k], [2. Block Kanal 1], ...
*/
*/
// Puffer erzeugen und mit Nullen initialiseren
// TODO: Fehlerbehandlung beim Speicherallozieren
/* Bugfix zu Bug #001:
Hier wurde der Puffer einfach um 1024 Felder verlngert.
Damit Funktioniert Wuschels ASIO4ALL jetzt. Ungeklrt aber
warum der Fehler auftrat?
2005-2-14
*/
2005-2-14
*/
m_pfBuffer = fm_falloc(m_uiBufferSize * m_uiChannels + /* >>> */ 1024 /* <<< */, false);
m_pfBuffer = fm_falloc( m_uiBufferSize * m_uiChannels + /* >>> */ 1024 /* <<< */, false );
Reset();
Reset();
}
ITADatasourceRealization::~ITADatasourceRealization() {
fm_free(m_pfBuffer);
ITADatasourceRealization::~ITADatasourceRealization()
{
fm_free( m_pfBuffer );
}
void ITADatasourceRealization::Reset() {
void ITADatasourceRealization::Reset()
{
m_uiReadCursor = 0;
m_uiWriteCursor = 0;
......@@ -93,22 +79,26 @@ void ITADatasourceRealization::Reset() {
m_iGBPEntrances = 0;
m_bGBPFirst = true;
fm_zero(m_pfBuffer, m_uiBufferSize * m_uiChannels + /* >>> */ 1024 /* <<< */);
fm_zero( m_pfBuffer, m_uiBufferSize * m_uiChannels + /* >>> */ 1024 /* <<< */ );
}
bool ITADatasourceRealization::HasStreamErrors() const {
return (m_iBufferUnderflows > 0) || (m_iBufferOverflows > 0) || (m_iGBPReentrances > 0);
bool ITADatasourceRealization::HasStreamErrors() const
{
return ( m_iBufferUnderflows > 0 ) || ( m_iBufferOverflows > 0 ) || ( m_iGBPReentrances > 0 );
}
ITADatasourceRealizationEventHandler* ITADatasourceRealization::GetStreamEventHandler() const {
ITADatasourceRealizationEventHandler* ITADatasourceRealization::GetStreamEventHandler() const
{
return m_pEventHandler;
}
void ITADatasourceRealization::SetStreamEventHandler(ITADatasourceRealizationEventHandler* pHandler) {
void ITADatasourceRealization::SetStreamEventHandler( ITADatasourceRealizationEventHandler* pHandler )
{
m_pEventHandler = pHandler;
}
const float* ITADatasourceRealization::GetBlockPointer(unsigned int uiChannel, const ITAStreamInfo* pStreamInfo) {
const float* ITADatasourceRealization::GetBlockPointer( unsigned int uiChannel, const ITAStreamInfo* pStreamInfo )
{
assert( uiChannel < m_uiChannels );
/*
......@@ -117,7 +107,8 @@ const float* ITADatasourceRealization::GetBlockPointer(unsigned int uiChannel, c
*
* WICHTIG: Dies sollte nicht passieren. Fehler beim anwendenden Programmierer!
*/
if (++m_iGBPEntrances > 1) {
if( ++m_iGBPEntrances > 1 )
{
--m_iGBPEntrances;
++m_iGBPReentrances;
return NULL;
......@@ -125,12 +116,16 @@ const float* ITADatasourceRealization::GetBlockPointer(unsigned int uiChannel, c
// Hook/Handler aufrufen
PreGetBlockPointer();
if (m_pEventHandler) m_pEventHandler->HandlePreGetBlockPointer(this, uiChannel);
if( m_pEventHandler )
m_pEventHandler->HandlePreGetBlockPointer( this, uiChannel );
if (m_bGBPFirst) {
if( m_bGBPFirst )
{
// Erster Eintritt in GBP seit letztem IBP => Daten produzieren
ProcessStream(pStreamInfo);
if (m_pEventHandler) m_pEventHandler->HandleProcessStream(this, pStreamInfo);
ProcessStream( pStreamInfo );
if( m_pEventHandler )
m_pEventHandler->HandleProcessStream( this, pStreamInfo );
m_bGBPFirst = false;
}
......@@ -145,45 +140,51 @@ const float* ITADatasourceRealization::GetBlockPointer(unsigned int uiChannel, c
*/
unsigned int uiLocalReadCursor = m_uiReadCursor;
if (uiLocalReadCursor == m_uiWriteCursor) {
if( uiLocalReadCursor == m_uiWriteCursor )
{
++m_iBufferUnderflows;
--m_iGBPEntrances;
return NULL;
}
--m_iGBPEntrances;
return m_pfBuffer + (uiChannel * m_uiBufferSize) + uiLocalReadCursor;
return m_pfBuffer + ( uiChannel * m_uiBufferSize ) + uiLocalReadCursor;
}
void ITADatasourceRealization::IncrementBlockPointer() {
void ITADatasourceRealization::IncrementBlockPointer()
{
unsigned int uiLocalReadCursor = m_uiReadCursor;
if (uiLocalReadCursor == m_uiWriteCursor)
if( uiLocalReadCursor == m_uiWriteCursor )
// Keine Daten im Ausgabepuffer? Kein Inkrement mglich! (Fehlerfall)
++m_iBufferUnderflows;
else
// Lesezeiger inkrementieren
m_uiReadCursor = (uiLocalReadCursor + m_uiBlocklength) % m_uiBufferSize;
m_uiReadCursor = ( uiLocalReadCursor + m_uiBlocklength ) % m_uiBufferSize;
m_bGBPFirst = true;
PostIncrementBlockPointer();
if (m_pEventHandler) m_pEventHandler->HandlePostIncrementBlockPointer(this);
if( m_pEventHandler )
m_pEventHandler->HandlePostIncrementBlockPointer( this );
}
float* ITADatasourceRealization::GetWritePointer(unsigned int uiChannel) {
float* ITADatasourceRealization::GetWritePointer( unsigned int uiChannel )
{
assert( uiChannel < m_uiChannels );
return m_pfBuffer + (uiChannel * m_uiBufferSize) + m_uiWriteCursor;
return m_pfBuffer + ( uiChannel * m_uiBufferSize ) + m_uiWriteCursor;
}
void ITADatasourceRealization::IncrementWritePointer() {
void ITADatasourceRealization::IncrementWritePointer()
{
// Lokaler Schreibcursor
unsigned int uiLocalWriteCursor = m_uiWriteCursor;
unsigned int uiNewWriteCursor = (uiLocalWriteCursor + m_uiBlocklength) % m_uiBufferSize;
unsigned int uiNewWriteCursor = ( uiLocalWriteCursor + m_uiBlocklength ) % m_uiBufferSize;
// Pufferberlauf
if (uiNewWriteCursor == m_uiReadCursor) {
if( uiNewWriteCursor == m_uiReadCursor )
{
++m_iBufferOverflows;
return;
}
......@@ -192,6 +193,6 @@ void ITADatasourceRealization::IncrementWritePointer() {
m_uiWriteCursor = uiNewWriteCursor;
}
void ITADatasourceRealizationEventHandler::HandlePreGetBlockPointer(ITADatasourceRealization* pSender, unsigned int uiChannel) {}
void ITADatasourceRealizationEventHandler::HandlePostIncrementBlockPointer(ITADatasourceRealization* pSender) {}
void ITADatasourceRealizationEventHandler::HandleProcessStream(ITADatasourceRealization* pSender, const ITAStreamInfo* pStreamInfo) {}
void ITADatasourceRealizationEventHandler::HandlePreGetBlockPointer( ITADatasourceRealization*, unsigned int ) {}
void ITADatasourceRealizationEventHandler::HandlePostIncrementBlockPointer( ITADatasourceRealization* ) {}
void ITADatasourceRealizationEventHandler::HandleProcessStream( ITADatasourceRealization*, const ITAStreamInfo* ) {}
......@@ -71,6 +71,8 @@ CITANetAudioStreamingServer::CITANetAudioStreamingServer()
, m_iEstimatedClientRingBufferFreeSamples( 0 )
, m_iClientRingBufferSize( 0 )
, m_dEstimatedCorrFactor( 1 )
, m_dStreamTimeStart( 0.0f )
, m_nStreamSampleCounts( 0 )
{
// Careful with this:
//SetPriority( VistaPriority::VISTA_MID_PRIORITY );
......@@ -160,6 +162,9 @@ bool CITANetAudioStreamingServer::LoopBody()
{
const double dNow = ITAClock::getDefaultClock()->getTime();
if( m_dStreamTimeStart == 0.0f )
m_dStreamTimeStart = dNow;
CITAServerLog oLog;
oLog.dWorldTimeStamp = dNow;
oLog.uiBlockId = ++m_iServerBlockId;
......@@ -182,10 +187,11 @@ bool CITANetAudioStreamingServer::LoopBody()
for( int i = 0; i < int( m_pInputStream->GetNumberOfChannels() ); i++ )
{
ITAStreamInfo oStreamInfo;
oStreamInfo.nSamples = m_iSendingBlockLength;
oStreamInfo.nSamples = ( m_nStreamSampleCounts += m_iSendingBlockLength );
oStreamInfo.dTimecode = dNow - m_dStreamTimeStart;
const float* pfData = m_pInputStream->GetBlockPointer( i, &oStreamInfo );
if( pfData != 0 )
if( pfData != nullptr )
m_sfTempTransmitBuffer[ i ].write( pfData, m_iSendingBlockLength, 0 );
}
......
#include <ITANetAudioStreamingServer.h>
#include <ITANetAudioSampleServer.h>
#include <ITANetAudioStream.h>
#include <ITAPortaudioInterface.h>
#include <ITAStreamFunctionGenerator.h>
......@@ -8,6 +9,7 @@
#include <ITAStreamProbe.h>
#include <ITAStreamPatchBay.h>
#include <ITAAsioInterface.h>
#include <ITAStreamInfo.h>
#include <VistaBase/VistaStreamUtils.h>
#include <VistaBase/VistaTimeUtils.h>
......@@ -24,7 +26,7 @@ const static string g_sInputFilePath = "gershwin-mono.wav";
const static int g_iServerPort = 12480;
const static double g_dSampleRate = 44100;
const static int g_iBlockLength = 512;
const static int g_iChannels = 160;
const static int g_iChannels = 2;
const static int g_iTargetLatencySamples = g_iBlockLength * 2;
const static int g_iRingerBufferCapacity = g_iBlockLength * 10;
const static double g_dDuration = 10.0f;
......@@ -34,53 +36,103 @@ const static string g_sAudioInterface = "ASIO4ALL v2";
//const static string g_sAudioInterface = "ASIO Hammerfall DSP";
const static bool g_bUseUDP = false;
class CServer : public VistaThread
class CSampleGenerator : public CITASampleProcessor
{
public:
inline CServer( const string& sInputFilePath )
inline CSampleGenerator()
: CITASampleProcessor( g_dSampleRate, g_iChannels, g_iBlockLength )
{};
inline void Process( const ITAStreamInfo* pStreamInfo )
{
pStreamingServer = new CITANetAudioStreamingServer;
pStreamingServer->SetDebuggingEnabled( true );
pStreamingServer->SetTargetLatencySamples( g_iTargetLatencySamples );
pStreamingServer->SetServerLogBaseName( "ITANetAudioTest_Server" );
pInputFile = new ITAFileDatasource( sInputFilePath, g_iBlockLength );
pInputFile->SetIsLooping( true );
assert( pInputFile->GetNumberOfChannels() == 1 );
pMuliplier = new ITAStreamMultiplier1N( pInputFile, g_iChannels );
pInputStreamProbe = new ITAStreamProbe( pMuliplier, "ITANetAudioTest.serverstream.wav" );
pStreamingServer->SetInputStream( pInputStreamProbe );
for( size_t c = 0; c < m_vvfSampleBuffer.size(); c++ )
{
for( size_t n = 0; n < m_vvfSampleBuffer[ c ].size(); n++ )
{
float fSample = ( c == 0 ? 1.0f : -1.0f ) * ( 1.0f - 2.0f * n / float( GetBlocklength() ) );
m_vvfSampleBuffer[ c ][ n ] = fSample;
}
}
};
};
class CSampleServerExample : public VistaThread
{
public:
inline CSampleServerExample()
{
m_pSampleGenerator = new CSampleGenerator();
m_pSampleServer = new CITANetAudioSampleServer( m_pSampleGenerator );
m_pSampleServer->SetDebuggingEnabled( true );
m_pSampleServer->SetTargetLatencySamples( g_iTargetLatencySamples );
m_pSampleServer->SetServerLogBaseName( "ITANetAudioTest_Server" );
Run();
};
inline ~CSampleServerExample()
{
delete m_pSampleServer;
delete m_pSampleGenerator;
};
void ThreadBody()
{
vstr::out() << "[ NetAudioTestServer ] Starting net audio sample server and waiting for client connections on '" << g_sServerName << "' on port " << g_iServerPort << endl;
m_pSampleServer->Start( g_sServerName, g_iServerPort, g_dSyncTimout, g_bUseUDP );
};
CITANetAudioSampleServer* m_pSampleServer;
CSampleGenerator* m_pSampleGenerator;
};
class CStreamServerExample : public VistaThread
{
public:
inline CStreamServerExample( const string& sInputFilePath )
{
m_pStreamingServer = new CITANetAudioStreamingServer;
m_pStreamingServer->SetDebuggingEnabled( true );
m_pStreamingServer->SetTargetLatencySamples( g_iTargetLatencySamples );
m_pStreamingServer->SetServerLogBaseName( "ITANetAudioTest_Server" );
m_pInputFile = new ITAFileDatasource( sInputFilePath, g_iBlockLength );
m_pInputFile->SetIsLooping( true );
assert( m_pInputFile->GetNumberOfChannels() == 1 );
m_pMultiplier = new ITAStreamMultiplier1N( m_pInputFile, g_iChannels );
m_pInputStreamProbe = new ITAStreamProbe( m_pMultiplier, "ITANetAudioTest.serverstream.wav" );
m_pStreamingServer->SetInputStream( m_pInputStreamProbe );
Run();
};
inline ~CServer( )
inline ~CStreamServerExample()
{
delete pInputFile;
delete pMuliplier;
delete pStreamingServer;
delete pInputStreamProbe;
delete m_pInputFile;
delete m_pMultiplier;
delete m_pStreamingServer;
delete m_pInputStreamProbe;
};
void ThreadBody( )
{
vstr::out() << "[ NetAudioTestServer ] Starting net audio server and waiting for client connections on '" << g_sServerName << "' on port " << g_iServerPort << endl;
pStreamingServer->Start( g_sServerName, g_iServerPort, g_dSyncTimout, g_bUseUDP );
m_pStreamingServer->Start( g_sServerName, g_iServerPort, g_dSyncTimout, g_bUseUDP );
};
private:
ITAFileDatasource* pInputFile;
ITAStreamMultiplier1N* pMuliplier;
CITANetAudioStreamingServer* pStreamingServer;
ITAStreamProbe* pInputStreamProbe;
ITAFileDatasource* m_pInputFile;
ITAStreamMultiplier1N* m_pMultiplier;
CITANetAudioStreamingServer* m_pStreamingServer;
ITAStreamProbe* m_pInputStreamProbe;
};
void run_test()
{
// Sample server (forked away into a thread)
CServer* pServer = new CServer( g_sInputFilePath );
//CStreamServerExample* pServer = new CStreamServerExample( g_sInputFilePath );
CSampleServerExample* pServer = new CSampleServerExample();
// Client dumping received stream and mixing down to two channels
CITANetAudioStream oNetAudioStream( g_iChannels, g_dSampleRate, g_iBlockLength, g_iRingerBufferCapacity );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment