1716 lines
59 KiB
C++
1716 lines
59 KiB
C++
|
#include "cwCommon.h"
|
||
|
#include "cwLog.h"
|
||
|
#include "cwCommonImpl.h"
|
||
|
#include "cwMem.h"
|
||
|
#include "cwTime.h"
|
||
|
#include "cwTextBuf.h"
|
||
|
#include "cwAudioPort.h"
|
||
|
#include "cwThread.h"
|
||
|
#include "cwAudioPortAlsa.h"
|
||
|
|
||
|
#include "alsa/asoundlib.h"
|
||
|
#include <unistd.h> // usleep
|
||
|
|
||
|
namespace cw
|
||
|
{
|
||
|
namespace audio
|
||
|
{
|
||
|
namespace device
|
||
|
{
|
||
|
namespace alsa
|
||
|
{
|
||
|
#define NAME_CHAR_CNT (255)
|
||
|
#define DESC_CHAR_CNT (255)
|
||
|
#define INIT_DEV_CNT (5)
|
||
|
|
||
|
//#define IMPULSE_FN "/home/kevin/temp/recd0.txt"
|
||
|
|
||
|
enum { kDfltPeriodsPerBuf = 2, kPollfdsArrayCnt=2 };
|
||
|
|
||
|
enum { kInFl=0x01, kOutFl=0x02 };
|
||
|
|
||
|
struct cmApRoot_str;
|
||
|
|
||
|
|
||
|
typedef struct devRecd_str
|
||
|
{
|
||
|
struct cmApRoot_str* rootPtr;
|
||
|
unsigned devIdx;
|
||
|
char nameStr[ NAME_CHAR_CNT ];
|
||
|
char descStr[ DESC_CHAR_CNT ];
|
||
|
unsigned flags;
|
||
|
|
||
|
unsigned framesPerCycle; // samples per sub-buffer
|
||
|
unsigned periodsPerBuf; // sub-buffers per buffer
|
||
|
snd_async_handler_t* ahandler;
|
||
|
unsigned srate; // device sample rate
|
||
|
|
||
|
unsigned iChCnt; // ch count
|
||
|
unsigned oChCnt;
|
||
|
|
||
|
unsigned iBits; // bits per sample
|
||
|
unsigned oBits;
|
||
|
|
||
|
bool iSignFl; // sample type is signed
|
||
|
bool oSignFl;
|
||
|
|
||
|
bool iSwapFl; // swap the sample bytes
|
||
|
bool oSwapFl;
|
||
|
|
||
|
unsigned iSigBits; // significant bits in each sample beginning
|
||
|
unsigned oSigBits; // with the most sig. bit.
|
||
|
|
||
|
|
||
|
device::sample_t* iBuf; // iBuf[ iFpc * iChCnt ]
|
||
|
device::sample_t* oBuf; // oBuf[ oFpc * oChCnt ]
|
||
|
|
||
|
unsigned oBufCnt; // count of buffers written
|
||
|
|
||
|
#ifdef IMPULSE_FN
|
||
|
int* recdBuf;
|
||
|
unsigned recdN;
|
||
|
unsigned recdIdx;
|
||
|
#endif
|
||
|
|
||
|
unsigned iFpC; // buffer frames per cycle (in ALSA this is call period_size)
|
||
|
unsigned oFpC;
|
||
|
|
||
|
snd_pcm_t* iPcmH; // device handle
|
||
|
snd_pcm_t* oPcmH;
|
||
|
|
||
|
unsigned iCbCnt; // callback count
|
||
|
unsigned oCbCnt;
|
||
|
|
||
|
unsigned iErrCnt; // error count
|
||
|
unsigned oErrCnt;
|
||
|
|
||
|
device::cbFunc_t cbPtr; // user callback
|
||
|
void* userCbPtr;
|
||
|
|
||
|
} cmApDevRecd_t;
|
||
|
|
||
|
typedef struct cmApPoll_str
|
||
|
{
|
||
|
cmApDevRecd_t* devPtr;
|
||
|
bool inputFl;
|
||
|
unsigned fdsCnt;
|
||
|
} cmApPollfdsDesc_t;
|
||
|
|
||
|
typedef struct cmApRoot_str
|
||
|
{
|
||
|
cmApDevRecd_t* devArray = nullptr; // array of device records
|
||
|
unsigned devCnt = 0; // count of actual dev recds in devArray[]
|
||
|
unsigned devAllocCnt = 0; // count of dev recds allocated in devArray[]
|
||
|
bool asyncFl = false; // true=use async callback false=use polling thread
|
||
|
|
||
|
thread::handle_t thH; // polling thread
|
||
|
|
||
|
unsigned pollfdsAllocCnt = 0; // 2*devCnt (max possible in+out handles)
|
||
|
struct pollfd* pollfds = nullptr; // pollfds[ pollfdsAllocCnt ]
|
||
|
cmApPollfdsDesc_t *pollfdsDesc = nullptr; // pollfdsDesc[ pollfdsAllocCnt ]
|
||
|
unsigned pollfdsCnt = 0; // count of active recds in pollfds[] and pollfdsDesc[]
|
||
|
|
||
|
} cmApRoot_t;
|
||
|
|
||
|
cmApRoot_t _cmApRoot;
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
kReadErrRId,
|
||
|
kWriteErrRId
|
||
|
};
|
||
|
|
||
|
void recdSetup(){}
|
||
|
void recdPush( unsigned typeId, unsigned devIdx, bool inputFl, unsigned arg, unsigned arg1 ){}
|
||
|
void recdStart( const cmApDevRecd_t* drp, bool inputFl ){}
|
||
|
void recdCb( const cmApDevRecd_t* drp, bool inputFl, unsigned frmCnt ){}
|
||
|
void recdSysErr( bool inputFl, int err ){}
|
||
|
void recdAppErr( const cmApDevRecd_t* drp, bool inputFl, int app, int err ){}
|
||
|
void recdRecover( const cmApDevRecd_t* drp, bool inputFl, int err, int line ){}
|
||
|
void recdPrint(){}
|
||
|
void recdFree(){}
|
||
|
|
||
|
|
||
|
rc_t _alsaErrorImpl( int alsaRC, const char* func, const char* fn, int lineNumb, const char* fmt, ... )
|
||
|
{
|
||
|
rc_t rc = kOkRC;
|
||
|
va_list vl0;
|
||
|
va_start(vl0,fmt);
|
||
|
|
||
|
if( alsaRC == 0 )
|
||
|
{
|
||
|
rc = logMsg( logGlobalHandle(), kError_LogLevel, func, fn, lineNumb, 0, kOpFailRC, fmt, vl0 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
va_list vl1;
|
||
|
va_copy(vl1,vl0);
|
||
|
|
||
|
int n = vsnprintf(NULL,0,fmt,vl0);
|
||
|
char msg0[n+1];
|
||
|
int m = vsnprintf(msg0,n+1,fmt,vl1);
|
||
|
|
||
|
cwAssert(n==m)
|
||
|
va_end(vl1);
|
||
|
|
||
|
const char* fmt1 = "%s ALSA Error:%s";
|
||
|
n = snprintf(NULL,0,fmt1,msg0,snd_strerror(alsaRC));
|
||
|
char msg1[n+1];
|
||
|
m = snprintf(msg1,n+1,fmt1,msg0,snd_strerror(alsaRC));
|
||
|
|
||
|
rc = logMsg( logGlobalHandle(), kError_LogLevel, func, fn, lineNumb, 0, kOpFailRC, "%s", msg1 );
|
||
|
}
|
||
|
|
||
|
va_end(vl0);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
#define _alsaError( alsaRC, fmt, ...) _alsaErrorImpl( alsaRC, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__ )
|
||
|
|
||
|
rc_t _alsaSetupErrorImpl( int alsaRC, bool inputFl, const cmApDevRecd_t* drp, const char* func, const char* fn, int lineNumb, const char* fmt, ... )
|
||
|
{
|
||
|
va_list vl0,vl1;
|
||
|
va_start(vl0,fmt);
|
||
|
va_copy(vl1,vl0);
|
||
|
int n = vsnprintf(NULL,0,fmt,vl0);
|
||
|
char msg[n+1];
|
||
|
int m = vsnprintf(msg,n+1,fmt,vl1);
|
||
|
cwAssert(m==n);
|
||
|
return _alsaErrorImpl( alsaRC, func, fn, lineNumb, "%s for %s '%s' : '%s'.",msg,inputFl ? "INPUT" : "OUTPUT", drp->nameStr, drp->descStr );
|
||
|
}
|
||
|
|
||
|
#define _alsaSetupError( alsaRC, inputFl, drp, fmt, ...) _alsaSetupErrorImpl( alsaRC, inputFl, drp, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__ )
|
||
|
|
||
|
const char* _cmApPcmStateToString( snd_pcm_state_t state )
|
||
|
{
|
||
|
switch( state )
|
||
|
{
|
||
|
case SND_PCM_STATE_OPEN: return "open";
|
||
|
case SND_PCM_STATE_SETUP: return "setup";
|
||
|
case SND_PCM_STATE_PREPARED: return "prepared";
|
||
|
case SND_PCM_STATE_RUNNING: return "running";
|
||
|
case SND_PCM_STATE_XRUN: return "xrun";
|
||
|
case SND_PCM_STATE_DRAINING: return "draining";
|
||
|
case SND_PCM_STATE_PAUSED: return "paused";
|
||
|
case SND_PCM_STATE_SUSPENDED: return "suspended";
|
||
|
case SND_PCM_STATE_DISCONNECTED: return "disconnected";
|
||
|
case SND_PCM_STATE_PRIVATE1: return "private1";
|
||
|
|
||
|
}
|
||
|
return "<invalid>";
|
||
|
}
|
||
|
|
||
|
void _cmApDevRtReport( cmApDevRecd_t* drp )
|
||
|
{
|
||
|
cwLogInfo("cb i:%i o:%i err i:%i o:%i",drp->iCbCnt,drp->oCbCnt,drp->iErrCnt,drp->oErrCnt);
|
||
|
|
||
|
if( drp->iPcmH != NULL )
|
||
|
cwLogInfo(" state i:%s",_cmApPcmStateToString(snd_pcm_state(drp->iPcmH)));
|
||
|
|
||
|
if( drp->oPcmH != NULL )
|
||
|
cwLogInfo(" o:%s",_cmApPcmStateToString(snd_pcm_state(drp->oPcmH)));
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
void _cmApDevReportFormats( snd_pcm_hw_params_t* hwParams )
|
||
|
{
|
||
|
snd_pcm_format_mask_t* mask;
|
||
|
|
||
|
snd_pcm_format_t fmt[] =
|
||
|
{
|
||
|
SND_PCM_FORMAT_S8,
|
||
|
SND_PCM_FORMAT_U8,
|
||
|
SND_PCM_FORMAT_S16_LE,
|
||
|
SND_PCM_FORMAT_S16_BE,
|
||
|
SND_PCM_FORMAT_U16_LE,
|
||
|
SND_PCM_FORMAT_U16_BE,
|
||
|
SND_PCM_FORMAT_S24_LE,
|
||
|
SND_PCM_FORMAT_S24_BE,
|
||
|
SND_PCM_FORMAT_U24_LE,
|
||
|
SND_PCM_FORMAT_U24_BE,
|
||
|
SND_PCM_FORMAT_S32_LE,
|
||
|
SND_PCM_FORMAT_S32_BE,
|
||
|
SND_PCM_FORMAT_U32_LE,
|
||
|
SND_PCM_FORMAT_U32_BE,
|
||
|
SND_PCM_FORMAT_FLOAT_LE,
|
||
|
SND_PCM_FORMAT_FLOAT_BE,
|
||
|
SND_PCM_FORMAT_FLOAT64_LE,
|
||
|
SND_PCM_FORMAT_FLOAT64_BE,
|
||
|
SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
|
||
|
SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
|
||
|
SND_PCM_FORMAT_MU_LAW,
|
||
|
SND_PCM_FORMAT_A_LAW,
|
||
|
SND_PCM_FORMAT_IMA_ADPCM,
|
||
|
SND_PCM_FORMAT_MPEG,
|
||
|
SND_PCM_FORMAT_GSM,
|
||
|
SND_PCM_FORMAT_SPECIAL,
|
||
|
SND_PCM_FORMAT_S24_3LE,
|
||
|
SND_PCM_FORMAT_S24_3BE,
|
||
|
SND_PCM_FORMAT_U24_3LE,
|
||
|
SND_PCM_FORMAT_U24_3BE,
|
||
|
SND_PCM_FORMAT_S20_3LE,
|
||
|
SND_PCM_FORMAT_S20_3BE,
|
||
|
SND_PCM_FORMAT_U20_3LE,
|
||
|
SND_PCM_FORMAT_U20_3BE,
|
||
|
SND_PCM_FORMAT_S18_3LE,
|
||
|
SND_PCM_FORMAT_S18_3BE,
|
||
|
SND_PCM_FORMAT_U18_3LE,
|
||
|
SND_PCM_FORMAT_U18_3BE,
|
||
|
SND_PCM_FORMAT_G723_24,
|
||
|
SND_PCM_FORMAT_G723_24_1B,
|
||
|
SND_PCM_FORMAT_G723_40,
|
||
|
SND_PCM_FORMAT_G723_40_1B,
|
||
|
SND_PCM_FORMAT_DSD_U8,
|
||
|
//SND_PCM_FORMAT_DSD_U16_LE,
|
||
|
//SND_PCM_FORMAT_DSD_U32_LE,
|
||
|
//SND_PCM_FORMAT_DSD_U16_BE,
|
||
|
//SND_PCM_FORMAT_DSD_U32_BE,
|
||
|
SND_PCM_FORMAT_UNKNOWN
|
||
|
};
|
||
|
|
||
|
snd_pcm_format_mask_alloca(&mask);
|
||
|
|
||
|
snd_pcm_hw_params_get_format_mask(hwParams,mask);
|
||
|
|
||
|
cwLogInfo("Formats: " );
|
||
|
|
||
|
int i;
|
||
|
for(i=0; fmt[i]!=SND_PCM_FORMAT_UNKNOWN; ++i)
|
||
|
if( snd_pcm_format_mask_test(mask, fmt[i] ))
|
||
|
cwLogInfo("%s%s",snd_pcm_format_name(fmt[i]), snd_pcm_format_cpu_endian(fmt[i]) ? " " : " (swap) ");
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
void _cmApDevReport( cmApDevRecd_t* drp )
|
||
|
{
|
||
|
bool inputFl = true;
|
||
|
snd_pcm_t* pcmH;
|
||
|
int err;
|
||
|
unsigned i;
|
||
|
|
||
|
//cmApRoot_t* p = drp->rootPtr;
|
||
|
|
||
|
cwLogInfo("%s %s Device:%s Desc:%s\n", drp->flags & kInFl ? "IN ":"", drp->flags & kOutFl ? "OUT ":"", drp->nameStr, drp->descStr);
|
||
|
|
||
|
for(i=0; i<2; i++,inputFl=!inputFl)
|
||
|
{
|
||
|
if( ((inputFl==true) && (drp->flags&kInFl)) || (((inputFl==false) && (drp->flags&kOutFl))))
|
||
|
{
|
||
|
const char* ioLabel = inputFl ? "In " : "Out";
|
||
|
|
||
|
// attempt to open the sub-device
|
||
|
if((err = snd_pcm_open(&pcmH,drp->nameStr,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,0)) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Attempt to open the PCM handle failed");
|
||
|
else
|
||
|
{
|
||
|
snd_pcm_hw_params_t* hwParams;
|
||
|
|
||
|
snd_pcm_hw_params_alloca(&hwParams);
|
||
|
memset(hwParams,0,snd_pcm_hw_params_sizeof());
|
||
|
|
||
|
// load the parameter record
|
||
|
if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error obtaining hw param record");
|
||
|
else
|
||
|
{
|
||
|
unsigned minChCnt=0,maxChCnt=0,minSrate=0,maxSrate=0;
|
||
|
snd_pcm_uframes_t minPeriodFrmCnt=0,maxPeriodFrmCnt=0,minBufFrmCnt=0,maxBufFrmCnt=0;
|
||
|
int dir;
|
||
|
|
||
|
|
||
|
// extract the min channel count
|
||
|
if((err = snd_pcm_hw_params_get_channels_min(hwParams, &minChCnt )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting min. channel count.");
|
||
|
|
||
|
// extract the max channel count
|
||
|
if((err = snd_pcm_hw_params_get_channels_max(hwParams, &maxChCnt )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting max. channel count.");
|
||
|
|
||
|
// extract the min srate
|
||
|
if((err = snd_pcm_hw_params_get_rate_min(hwParams, &minSrate,&dir )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting min. sample rate.");
|
||
|
|
||
|
// extract the max srate
|
||
|
if((err = snd_pcm_hw_params_get_rate_max(hwParams, &maxSrate,&dir )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting max. sample rate.");
|
||
|
|
||
|
// extract the min period
|
||
|
if((err = snd_pcm_hw_params_get_period_size_min(hwParams, &minPeriodFrmCnt,&dir )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting min. period frame count.");
|
||
|
|
||
|
// extract the max period
|
||
|
if((err = snd_pcm_hw_params_get_period_size_max(hwParams, &maxPeriodFrmCnt,&dir )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting max. period frame count.");
|
||
|
|
||
|
// extract the min buf
|
||
|
if((err = snd_pcm_hw_params_get_buffer_size_min(hwParams, &minBufFrmCnt )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting min. period frame count.");
|
||
|
|
||
|
// extract the max buffer
|
||
|
if((err = snd_pcm_hw_params_get_buffer_size_max(hwParams, &maxBufFrmCnt )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"Error getting max. period frame count.");
|
||
|
|
||
|
cwLogInfo("%s chs:%i - %i srate:%i - %i period:%i - %i buf:%i - %i half duplex only:%s joint duplex:%s",
|
||
|
ioLabel,minChCnt,maxChCnt,minSrate,maxSrate,minPeriodFrmCnt,maxPeriodFrmCnt,minBufFrmCnt,maxBufFrmCnt,
|
||
|
(snd_pcm_hw_params_is_half_duplex(hwParams) ? "yes" : "no"),
|
||
|
(snd_pcm_hw_params_is_joint_duplex(hwParams) ? "yes" : "no"));
|
||
|
|
||
|
_cmApDevReportFormats( hwParams );
|
||
|
}
|
||
|
|
||
|
if((err = snd_pcm_close(pcmH)) < 0)
|
||
|
_alsaSetupError(err,inputFl,drp,"Error closing PCM handle");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Called by cmApInitialize() to append a cmApDevRecd to the _cmApRoot.devArray[].
|
||
|
void _cmApDevAppend( cmApRoot_t* p, cmApDevRecd_t* drp )
|
||
|
{
|
||
|
if( p->devCnt == p->devAllocCnt )
|
||
|
{
|
||
|
p->devArray = memResizeZ<cmApDevRecd_t>( p->devArray, p->devAllocCnt + INIT_DEV_CNT );
|
||
|
p->devAllocCnt += INIT_DEV_CNT;
|
||
|
}
|
||
|
|
||
|
drp->devIdx = p->devCnt; // set the device index
|
||
|
drp->rootPtr = p; // set the pointer back to the root
|
||
|
|
||
|
#ifdef IMPULSE_FN
|
||
|
drp->recdN = 44100*2*2;
|
||
|
drp->recdBuf = cmMemAllocZ(int,drp->recdN);
|
||
|
drp->recdIdx = 0;
|
||
|
#endif
|
||
|
|
||
|
memcpy(p->devArray + p->devCnt, drp, sizeof(cmApDevRecd_t));
|
||
|
|
||
|
++p->devCnt;
|
||
|
}
|
||
|
|
||
|
rc_t _cmApDevShutdown( cmApRoot_t* p, cmApDevRecd_t* drp, bool inputFl )
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
snd_pcm_t** pcmH = inputFl ? &drp->iPcmH : &drp->oPcmH;
|
||
|
|
||
|
if( *pcmH != NULL )
|
||
|
{
|
||
|
if((err = snd_pcm_close(*pcmH)) < 0 )
|
||
|
{
|
||
|
return _alsaSetupError(err,inputFl,drp,"Error closing device handle.");
|
||
|
}
|
||
|
|
||
|
*pcmH = NULL;
|
||
|
}
|
||
|
|
||
|
return kOkRC;
|
||
|
}
|
||
|
|
||
|
int _cmApDevOpen( snd_pcm_t** pcmHPtr, const char* devNameStr, bool inputFl )
|
||
|
{
|
||
|
int cnt = 0;
|
||
|
int err;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
if((err = snd_pcm_open(pcmHPtr,devNameStr,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,0)) < 0 )
|
||
|
{
|
||
|
cnt++;
|
||
|
usleep(10000); // sleep for 10 milliseconds
|
||
|
}
|
||
|
|
||
|
}while(cnt<100 && err == -EBUSY );
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
void _cmApXrun_recover( snd_pcm_t* pcmH, int err, cmApDevRecd_t* drp, bool inputFl, int line )
|
||
|
{
|
||
|
char dirCh = inputFl ? 'I' : 'O';
|
||
|
|
||
|
inputFl ? drp->iErrCnt++ : drp->oErrCnt++;
|
||
|
|
||
|
recdRecover(drp,inputFl,err,line);
|
||
|
|
||
|
// -EPIPE signals and over/underrun (see pcm.c example xrun_recovery())
|
||
|
switch( err )
|
||
|
{
|
||
|
case -EPIPE:
|
||
|
{
|
||
|
|
||
|
int silentFl = 1;
|
||
|
if((err = snd_pcm_recover( pcmH, err, silentFl )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"recover failed.");
|
||
|
|
||
|
if( inputFl )
|
||
|
{
|
||
|
if((err= snd_pcm_prepare(pcmH)) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"re-prepare failed.");
|
||
|
else
|
||
|
if((err = snd_pcm_start(pcmH)) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"restart failed.");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/*
|
||
|
_cmApWriteBuf(pcmH, NULL, drp->oChCnt, drp->oFpC );
|
||
|
_cmApWriteBuf(pcmH, NULL, drp->oChCnt, drp->oFpC );
|
||
|
|
||
|
if((err = snd_pcm_prepare(pcmH))<0)
|
||
|
_alsaSetupError(err,inputFl,drp,"Recovery failed.\n");
|
||
|
else
|
||
|
if((err = snd_pcm_resume(pcmH))<0)
|
||
|
_alsaSetupError(err,inputFl,drp,"Resume failed.\n");
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
|
||
|
printf("EPIPE %c %i %i %i\n",dirCh,drp->devIdx,drp->oCbCnt,line);
|
||
|
|
||
|
//if((err = snd_pcm_prepare(pcmH))<0)
|
||
|
// _devSetupError(err,inputFl,*drp,"Recovery failed.\n");
|
||
|
//else
|
||
|
// if((err = snd_pcm_resume(pcmH))<0)
|
||
|
// _devSetupError(err,inputFl,*drp,"Resume failed.\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case -ESTRPIPE:
|
||
|
{
|
||
|
int silentFl = 1;
|
||
|
if((err = snd_pcm_recover( pcmH, err, silentFl )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,drp,"recover failed.");
|
||
|
|
||
|
printf("audio port impl ESTRPIPE:%c\n",dirCh);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case -EBADFD:
|
||
|
{
|
||
|
_alsaSetupError(err,inputFl,drp,"%s failed.",inputFl ? "Read" : "Write" );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
_alsaSetupError(err,inputFl,drp,"Unknown rd/wr error.\n");
|
||
|
|
||
|
} // switch
|
||
|
|
||
|
}
|
||
|
|
||
|
void _cmApStateRecover( snd_pcm_t* pcmH, cmApDevRecd_t* drp, bool inputFl )
|
||
|
{
|
||
|
int err = 0;
|
||
|
|
||
|
switch( snd_pcm_state(pcmH))
|
||
|
{
|
||
|
case SND_PCM_STATE_XRUN:
|
||
|
err = -EPIPE;
|
||
|
break;
|
||
|
|
||
|
case SND_PCM_STATE_SUSPENDED:
|
||
|
err = -ESTRPIPE;
|
||
|
break;
|
||
|
|
||
|
case SND_PCM_STATE_OPEN:
|
||
|
case SND_PCM_STATE_SETUP:
|
||
|
case SND_PCM_STATE_PREPARED:
|
||
|
case SND_PCM_STATE_RUNNING:
|
||
|
case SND_PCM_STATE_DRAINING:
|
||
|
case SND_PCM_STATE_PAUSED:
|
||
|
case SND_PCM_STATE_DISCONNECTED:
|
||
|
case SND_PCM_STATE_PRIVATE1:
|
||
|
//case SND_PCM_STATE_LAST:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( err < 0 )
|
||
|
_cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ );
|
||
|
|
||
|
}
|
||
|
|
||
|
void _cmApS24_3BE_to_Float( const char* x, device::sample_t* y, unsigned n )
|
||
|
{
|
||
|
unsigned i;
|
||
|
for(i=0; i<n; ++i,x+=3)
|
||
|
{
|
||
|
int s = (((int)x[0])<<16) + (((int)x[1])<<8) + (((int)x[2]));
|
||
|
y[i] = ((device::sample_t)s)/0x7fffff;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _cmApS24_3BE_from_Float( const device::sample_t* x, char* y, unsigned n )
|
||
|
{
|
||
|
unsigned i;
|
||
|
for(i=0; i<n; ++i)
|
||
|
{
|
||
|
int s = x[i] * 0x7fffff;
|
||
|
y[i*3+2] = (char)((s & 0x7f0000) >> 16);
|
||
|
y[i*3+1] = (char)((s & 0x00ff00) >> 8);
|
||
|
y[i*3+0] = (char)((s & 0x0000ff) >> 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Returns count of frames written on success or < 0 on error;
|
||
|
// set smpPtr to NULL to write a buffer of silence
|
||
|
int _cmApWriteBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, const device::sample_t* sp, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits )
|
||
|
{
|
||
|
int err = 0;
|
||
|
unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8;
|
||
|
unsigned smpCnt = chCnt * frmCnt;
|
||
|
unsigned byteCnt = bytesPerSmp * smpCnt;
|
||
|
const device::sample_t* ep = sp + smpCnt;
|
||
|
char obuf[ byteCnt ];
|
||
|
|
||
|
// if no output was given then fill the device buffer with zeros
|
||
|
if( sp == NULL )
|
||
|
memset(obuf,0,byteCnt);
|
||
|
else
|
||
|
{
|
||
|
// otherwise convert the floating point samples to integers
|
||
|
switch( bits )
|
||
|
{
|
||
|
case 8:
|
||
|
{
|
||
|
char* dp = (char*)obuf;
|
||
|
while( sp < ep )
|
||
|
*dp++ = (char)(*sp++ * 0x7f);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 16:
|
||
|
{
|
||
|
short* dp = (short*)obuf;
|
||
|
while( sp < ep )
|
||
|
*dp++ = (short)(*sp++ * 0x7fff);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 24:
|
||
|
{
|
||
|
// for use w/ MBox
|
||
|
//_cmApS24_3BE_from_Float(sp, obuf, ep-sp );
|
||
|
|
||
|
int* dp = (int*)obuf;
|
||
|
while( sp < ep )
|
||
|
*dp++ = (int)(*sp++ * 0x7fffff);
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 32:
|
||
|
{
|
||
|
int* dp = (int*)obuf;
|
||
|
|
||
|
while( sp < ep )
|
||
|
*dp++ = (int)(*sp++ * 0x7fffffff);
|
||
|
|
||
|
#ifdef IMPLUSE_FN
|
||
|
int* tmp = (int*)obuf;
|
||
|
unsigned ii = 0;
|
||
|
if( drp->oBufCnt < 3 )
|
||
|
for(; ii<32; ++ii)
|
||
|
tmp[ii] = 0x7fffffff;
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// send the bytes to the device
|
||
|
err = snd_pcm_writei( pcmH, obuf, frmCnt );
|
||
|
|
||
|
++drp->oBufCnt;
|
||
|
|
||
|
if( err < 0 )
|
||
|
{
|
||
|
recdAppErr(drp,false,kWriteErrRId,err);
|
||
|
_alsaSetupError( err, false, drp, "ALSA write error" );
|
||
|
}
|
||
|
else
|
||
|
if( err > 0 && ((unsigned)err) != frmCnt )
|
||
|
{
|
||
|
_alsaSetupError( 0, false, drp, "Actual count of bytes written did not match the count provided." );
|
||
|
}
|
||
|
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Returns frames read on success or < 0 on error.
|
||
|
// Set smpPtr to NULL to read the incoming buffer and discard it
|
||
|
int _cmApReadBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, device::sample_t* smpPtr, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits )
|
||
|
{
|
||
|
int err = 0;
|
||
|
unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8;
|
||
|
unsigned smpCnt = chCnt * frmCnt;
|
||
|
unsigned byteCnt = smpCnt * bytesPerSmp;
|
||
|
|
||
|
char buf[ byteCnt ];
|
||
|
|
||
|
// get the incoming samples into buf[] ...
|
||
|
err = snd_pcm_readi(pcmH,buf,frmCnt);
|
||
|
|
||
|
// if a read error occurred
|
||
|
if( err < 0 )
|
||
|
{
|
||
|
recdAppErr(drp,true,kReadErrRId,err);
|
||
|
_alsaSetupError( err, false, drp, "ALSA read error" );
|
||
|
}
|
||
|
else
|
||
|
if( err > 0 && ((unsigned)err) != frmCnt )
|
||
|
{
|
||
|
_alsaSetupError( 0, false, drp, "Actual count of bytes read did not match the count requested." );
|
||
|
}
|
||
|
|
||
|
// if no buffer was given then there is nothing else to do
|
||
|
if( smpPtr == NULL )
|
||
|
return err;
|
||
|
|
||
|
// setup the return buffer
|
||
|
device::sample_t* dp = smpPtr;
|
||
|
device::sample_t* ep = dp + std::min(smpCnt,err*chCnt);
|
||
|
|
||
|
switch(bits)
|
||
|
{
|
||
|
case 8:
|
||
|
{
|
||
|
char* sp = buf;
|
||
|
while(dp < ep)
|
||
|
*dp++ = ((device::sample_t)*sp++) / 0x7f;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 16:
|
||
|
{
|
||
|
short* sp = (short*)buf;
|
||
|
while(dp < ep)
|
||
|
*dp++ = ((device::sample_t)*sp++) / 0x7fff;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 24:
|
||
|
{
|
||
|
// For use with MBox
|
||
|
//_cmApS24_3BE_to_Float(buf, dp, ep-dp );
|
||
|
int* sp = (int*)buf;
|
||
|
while(dp < ep)
|
||
|
*dp++ = ((device::sample_t)*sp++) / 0x7fffff;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
|
||
|
case 32:
|
||
|
{
|
||
|
int* sp = (int*)buf;
|
||
|
// The delta1010 (ICE1712) uses only the 24 highest bits according to
|
||
|
//
|
||
|
// http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html
|
||
|
// <snip> The example: ICE1712 chips support 32-bit sample processing,
|
||
|
// but low byte is ignored (playback) or zero (capture).
|
||
|
//
|
||
|
int mv = sigBits==24 ? 0x7fffff00 : 0x7fffffff;
|
||
|
while(dp < ep)
|
||
|
*dp++ = ((device::sample_t)*sp++) / mv;
|
||
|
|
||
|
#ifdef IMPULSE_FN
|
||
|
sp = (int*)buf;
|
||
|
int* esp = sp + smpCnt;
|
||
|
for(; sp<esp && drp->recdIdx < drp->recdN; ++drp->recdIdx)
|
||
|
drp->recdBuf[drp->recdIdx] = *sp++;
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
{ cwAssert(0); }
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
|
||
|
}
|
||
|
|
||
|
void _cmApStaticAsyncHandler( snd_async_handler_t* ahandler )
|
||
|
{
|
||
|
int err;
|
||
|
snd_pcm_sframes_t avail;
|
||
|
cmApDevRecd_t* drp = (cmApDevRecd_t*)snd_async_handler_get_callback_private(ahandler);
|
||
|
snd_pcm_t* pcmH = snd_async_handler_get_pcm(ahandler);
|
||
|
bool inputFl = snd_pcm_stream(pcmH) == SND_PCM_STREAM_CAPTURE;
|
||
|
device::sample_t* b = inputFl ? drp->iBuf : drp->oBuf;
|
||
|
unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
|
||
|
unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
|
||
|
device::audioPacket_t pkt;
|
||
|
|
||
|
inputFl ? drp->iCbCnt++ : drp->oCbCnt++;
|
||
|
|
||
|
pkt.devIdx = drp->devIdx;
|
||
|
pkt.begChIdx = 0;
|
||
|
pkt.chCnt = chCnt;
|
||
|
pkt.audioFramesCnt = frmCnt;
|
||
|
pkt.bitsPerSample = 32;
|
||
|
pkt.flags = kInterleavedApFl | kFloatApFl;
|
||
|
pkt.audioBytesPtr = b;
|
||
|
pkt.userCbPtr = drp->userCbPtr;
|
||
|
|
||
|
recdCb(drp,inputFl,0);
|
||
|
|
||
|
_cmApStateRecover( pcmH, drp, inputFl );
|
||
|
|
||
|
while( (avail = snd_pcm_avail_update(pcmH)) >= (snd_pcm_sframes_t)frmCnt )
|
||
|
{
|
||
|
|
||
|
// Handle input
|
||
|
if( inputFl )
|
||
|
{
|
||
|
// read samples from the device
|
||
|
if((err = _cmApReadBuf(drp,pcmH,drp->iBuf,chCnt,frmCnt,drp->iBits,drp->oBits)) > 0 )
|
||
|
{
|
||
|
pkt.audioFramesCnt = err;
|
||
|
|
||
|
drp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Handle output
|
||
|
else
|
||
|
{
|
||
|
// callback to fill the buffer
|
||
|
drp->cbPtr(NULL,0,&pkt,1);
|
||
|
|
||
|
// note that the application may return fewer samples than were requested
|
||
|
err = _cmApWriteBuf(drp, pcmH, pkt.audioFramesCnt < frmCnt ? NULL : drp->oBuf,chCnt,frmCnt,drp->oBits,drp->oSigBits);
|
||
|
|
||
|
}
|
||
|
|
||
|
// Handle read/write errors
|
||
|
if( err < 0 )
|
||
|
{
|
||
|
inputFl ? drp->iErrCnt++ : drp->oErrCnt++;
|
||
|
_cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// _cmApStateRecover( pcmH, drp, inputFl );
|
||
|
}
|
||
|
|
||
|
} // while
|
||
|
|
||
|
}
|
||
|
|
||
|
bool _cmApThreadFunc(void* param)
|
||
|
{
|
||
|
cmApRoot_t* p = (cmApRoot_t*)param;
|
||
|
int result;
|
||
|
bool retFl = true;
|
||
|
|
||
|
switch( result = poll(p->pollfds, p->pollfdsCnt, 250) )
|
||
|
{
|
||
|
case 0:
|
||
|
// time out
|
||
|
break;
|
||
|
|
||
|
case -1:
|
||
|
_alsaError(errno,"Poll fail.");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
cwAssert( result > 0 );
|
||
|
|
||
|
unsigned i;
|
||
|
|
||
|
// for each i/o stream
|
||
|
for(i=0; i<p->pollfdsCnt; ++i)
|
||
|
{
|
||
|
cmApDevRecd_t* drp = p->pollfdsDesc[i].devPtr;
|
||
|
bool inputFl = p->pollfdsDesc[i].inputFl;
|
||
|
snd_pcm_t* pcmH = inputFl ? drp->iPcmH : drp->oPcmH;
|
||
|
unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
|
||
|
unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
|
||
|
device::sample_t* b = inputFl ? drp->iBuf : drp->oBuf;
|
||
|
unsigned short revents = 0;
|
||
|
int err;
|
||
|
device::audioPacket_t pkt;
|
||
|
snd_pcm_uframes_t avail_frames;
|
||
|
|
||
|
inputFl ? drp->iCbCnt++ : drp->oCbCnt++;
|
||
|
|
||
|
pkt.devIdx = drp->devIdx;
|
||
|
pkt.begChIdx = 0;
|
||
|
pkt.chCnt = chCnt;
|
||
|
pkt.audioFramesCnt = frmCnt;
|
||
|
pkt.bitsPerSample = 32;
|
||
|
pkt.flags = kInterleavedApFl | kFloatApFl;
|
||
|
pkt.audioBytesPtr = b;
|
||
|
pkt.userCbPtr = drp->userCbPtr;
|
||
|
|
||
|
inputFl ? drp->iCbCnt++ : drp->oCbCnt++;
|
||
|
|
||
|
// get the timestamp for this buffer
|
||
|
if((err = snd_pcm_htimestamp(pcmH,&avail_frames,&pkt.timeStamp)) != 0 )
|
||
|
{
|
||
|
_alsaSetupError( err, p->pollfdsDesc[i].inputFl, drp, "Get timestamp error.");
|
||
|
pkt.timeStamp.tv_sec = 0;
|
||
|
pkt.timeStamp.tv_nsec = 0;
|
||
|
}
|
||
|
|
||
|
// Note that based on experimenting with the timestamp and the current
|
||
|
// clock_gettime(CLOCK_MONOTONIC) time it appears that the time stamp
|
||
|
// marks the end of the current buffer - so in fact the time stamp should
|
||
|
// be backed up by the availble sample count period to get the time of the
|
||
|
// first sample in the buffer
|
||
|
/*
|
||
|
unsigned avail_nano_secs = (unsigned)(avail_frames * (1000000000.0/drp->srate));
|
||
|
if( pkt.timeStamp.tv_nsec > avail_nano_secs )
|
||
|
pkt.timeStamp.tv_nsec -= avail_nano_secs;
|
||
|
else
|
||
|
{
|
||
|
pkt.timeStamp.tv_sec -= 1;
|
||
|
pkt.timeStamp.tv_nsec = 1000000000 - avail_nano_secs;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
//printf("AUDI: %ld %ld\n",pkt.timeStamp.tv_sec,pkt.timeStamp.tv_nsec);
|
||
|
//cmTimeSpec_t t;
|
||
|
//clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&t);
|
||
|
//printf("AUDI: %ld %ld\n",t.tv_sec,t.tv_nsec);
|
||
|
|
||
|
|
||
|
switch( snd_pcm_state(pcmH) )
|
||
|
{
|
||
|
case SND_PCM_STATE_OPEN:
|
||
|
case SND_PCM_STATE_SETUP:
|
||
|
case SND_PCM_STATE_PREPARED:
|
||
|
case SND_PCM_STATE_DRAINING:
|
||
|
case SND_PCM_STATE_PAUSED:
|
||
|
case SND_PCM_STATE_DISCONNECTED:
|
||
|
case SND_PCM_STATE_PRIVATE1:
|
||
|
continue;
|
||
|
|
||
|
case SND_PCM_STATE_RUNNING:
|
||
|
case SND_PCM_STATE_XRUN:
|
||
|
case SND_PCM_STATE_SUSPENDED:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(( err = snd_pcm_poll_descriptors_revents(pcmH, p->pollfds + i, 1 , &revents)) != 0 )
|
||
|
{
|
||
|
_alsaSetupError( err, p->pollfdsDesc[i].inputFl, drp, "Return poll events failed.");
|
||
|
retFl = false;
|
||
|
goto errLabel;
|
||
|
}
|
||
|
|
||
|
if(revents & POLLERR)
|
||
|
{
|
||
|
_alsaSetupError( err, p->pollfdsDesc[i].inputFl, drp, "Poll error.");
|
||
|
_cmApStateRecover( pcmH, drp, inputFl );
|
||
|
//goto errLabel;
|
||
|
}
|
||
|
|
||
|
if( inputFl && (revents & POLLIN) )
|
||
|
{
|
||
|
if((err = _cmApReadBuf(drp,pcmH,drp->iBuf,chCnt,frmCnt,drp->iBits,drp->oBits)) > 0 )
|
||
|
{
|
||
|
pkt.audioFramesCnt = err;
|
||
|
drp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !inputFl && (revents & POLLOUT) )
|
||
|
{
|
||
|
|
||
|
/*
|
||
|
unsigned srate = 96;
|
||
|
cmTimeSpec_t t1;
|
||
|
static cmTimeSpec_t t0 = {0,0};
|
||
|
clock_gettime(CLOCK_MONOTONIC,&t1);
|
||
|
|
||
|
// time since the time-stamp was generated
|
||
|
unsigned smp = (srate * (t1.tv_nsec - pkt.timeStamp.tv_nsec)) / 1000000;
|
||
|
|
||
|
// time since the last output buffer was sent
|
||
|
unsigned dsmp = (srate * (t1.tv_nsec - t0.tv_nsec)) / 1000000;
|
||
|
printf("%i %ld %i : %ld %ld -> %ld %ld\n",smp,avail_frames,dsmp,pkt.timeStamp.tv_sec,pkt.timeStamp.tv_nsec,t1.tv_sec,t1.tv_nsec);
|
||
|
t0 = t1;
|
||
|
*/
|
||
|
|
||
|
// callback to fill the buffer
|
||
|
drp->cbPtr(NULL,0,&pkt,1);
|
||
|
|
||
|
// note that the application may return fewer samples than were requested
|
||
|
err = _cmApWriteBuf(drp, pcmH, pkt.audioFramesCnt < frmCnt ? NULL : drp->oBuf,chCnt,frmCnt,drp->oBits,drp->oSigBits);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
errLabel:
|
||
|
return retFl;
|
||
|
}
|
||
|
|
||
|
|
||
|
rc_t _cmApDevSetup( cmApDevRecd_t *drp, unsigned srate, unsigned framesPerCycle, unsigned periodsPerBuf )
|
||
|
{
|
||
|
int err;
|
||
|
int dir;
|
||
|
unsigned i;
|
||
|
rc_t rc = kOkRC;
|
||
|
bool inputFl = true;
|
||
|
snd_pcm_uframes_t periodFrameCnt = framesPerCycle;
|
||
|
snd_pcm_uframes_t bufferFrameCnt;
|
||
|
unsigned bits = 0;
|
||
|
int sig_bits = 0;
|
||
|
bool signFl = true;
|
||
|
bool swapFl = false;
|
||
|
cmApRoot_t* p = drp->rootPtr;
|
||
|
|
||
|
snd_pcm_format_t fmt[] =
|
||
|
{
|
||
|
SND_PCM_FORMAT_S32_LE,
|
||
|
SND_PCM_FORMAT_S32_BE,
|
||
|
SND_PCM_FORMAT_S24_LE,
|
||
|
SND_PCM_FORMAT_S24_BE,
|
||
|
SND_PCM_FORMAT_S24_3LE,
|
||
|
SND_PCM_FORMAT_S24_3BE,
|
||
|
SND_PCM_FORMAT_S16_LE,
|
||
|
SND_PCM_FORMAT_S16_BE,
|
||
|
};
|
||
|
|
||
|
|
||
|
// setup input, then output device
|
||
|
for(i=0; i<2; i++,inputFl=!inputFl)
|
||
|
{
|
||
|
unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
|
||
|
snd_pcm_uframes_t actFpC = 0;
|
||
|
|
||
|
// if this is the in/out pass and the in/out flag is set
|
||
|
if( ((inputFl==true) && (drp->flags & kInFl)) || ((inputFl==false) && (drp->flags & kOutFl)) )
|
||
|
{
|
||
|
snd_pcm_t* pcmH = NULL;
|
||
|
rc_t rc0;
|
||
|
|
||
|
if((rc0 = _cmApDevShutdown(p, drp, inputFl )) != kOkRC )
|
||
|
rc = rc0;
|
||
|
|
||
|
// attempt to open the sub-device
|
||
|
if((err = snd_pcm_open(&pcmH,drp->nameStr, inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Unable to open the PCM handle");
|
||
|
else
|
||
|
{
|
||
|
|
||
|
snd_pcm_hw_params_t* hwParams;
|
||
|
snd_pcm_sw_params_t* swParams;
|
||
|
|
||
|
// prepare the hwParam recd
|
||
|
snd_pcm_hw_params_alloca(&hwParams);
|
||
|
memset(hwParams,0,snd_pcm_hw_params_sizeof());
|
||
|
|
||
|
|
||
|
// load the hw parameter record
|
||
|
if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error obtaining hw param record");
|
||
|
else
|
||
|
{
|
||
|
if((err = snd_pcm_hw_params_set_rate_resample(pcmH,hwParams,0)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp,"Unable to disable the ALSA sample rate converter.");
|
||
|
|
||
|
|
||
|
if((err = snd_pcm_hw_params_set_channels(pcmH,hwParams,chCnt)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp,"Unable to set channel count to: %i",chCnt);
|
||
|
|
||
|
|
||
|
if((err = snd_pcm_hw_params_set_rate(pcmH,hwParams,srate,0)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to set sample rate to: %i",srate);
|
||
|
|
||
|
|
||
|
if((err = snd_pcm_hw_params_set_access(pcmH,hwParams,SND_PCM_ACCESS_RW_INTERLEAVED )) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to set access to: RW Interleaved");
|
||
|
|
||
|
// select the format width
|
||
|
int j;
|
||
|
int fmtN = sizeof(fmt)/sizeof(fmt[0]);
|
||
|
for(j=0; j<fmtN; ++j)
|
||
|
if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,fmt[j])) >= 0 )
|
||
|
break;
|
||
|
|
||
|
if( j == fmtN )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to set format to: S16");
|
||
|
else
|
||
|
{
|
||
|
bits = snd_pcm_format_width(fmt[j]); // bits per sample
|
||
|
signFl = snd_pcm_format_signed(fmt[j]);
|
||
|
swapFl = !snd_pcm_format_cpu_endian(fmt[j]);
|
||
|
}
|
||
|
|
||
|
sig_bits = snd_pcm_hw_params_get_sbits(hwParams);
|
||
|
|
||
|
snd_pcm_uframes_t ps_min,ps_max;
|
||
|
if((err = snd_pcm_hw_params_get_period_size_min(hwParams,&ps_min,NULL)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to get the minimum period size.");
|
||
|
|
||
|
if((err = snd_pcm_hw_params_get_period_size_max(hwParams,&ps_max,NULL)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to get the maximum period size.");
|
||
|
|
||
|
if( periodFrameCnt < ps_min )
|
||
|
periodFrameCnt = ps_min;
|
||
|
else
|
||
|
if( periodFrameCnt > ps_max )
|
||
|
periodFrameCnt = ps_max;
|
||
|
|
||
|
if((err = snd_pcm_hw_params_set_period_size_near(pcmH,hwParams,&periodFrameCnt,NULL)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to set period to %i.",periodFrameCnt);
|
||
|
|
||
|
bufferFrameCnt = periodFrameCnt * periodsPerBuf + 1;
|
||
|
|
||
|
if((err = snd_pcm_hw_params_set_buffer_size_near(pcmH,hwParams,&bufferFrameCnt)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Unable to set buffer to %i.",bufferFrameCnt);
|
||
|
|
||
|
|
||
|
// Note: snd_pcm_hw_params() automatically calls snd_pcm_prepare()
|
||
|
if((err = snd_pcm_hw_params(pcmH,hwParams)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl, drp, "Parameter application failed.");
|
||
|
|
||
|
|
||
|
//_reportActualParams( hwParams, inputFl, dr, srate, periodFrameCnt, bufferFrameCnt );
|
||
|
}
|
||
|
|
||
|
// prepare the sw param recd
|
||
|
snd_pcm_sw_params_alloca(&swParams);
|
||
|
memset(swParams,0,snd_pcm_sw_params_sizeof());
|
||
|
|
||
|
// load the sw param recd
|
||
|
if((err = snd_pcm_sw_params_current(pcmH,swParams)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error obtaining sw param record.");
|
||
|
else
|
||
|
{
|
||
|
|
||
|
if((err = snd_pcm_sw_params_set_start_threshold(pcmH,swParams, inputFl ? 0x7fffffff : periodFrameCnt)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error seting the start threshold.");
|
||
|
|
||
|
// setting the stop-threshold to twice the buffer frame count is intended to stop spurious
|
||
|
// XRUN states - it will also mean that there will have no direct way of knowing about a
|
||
|
// in/out buffer over/under run.
|
||
|
if((err = snd_pcm_sw_params_set_stop_threshold(pcmH,swParams,bufferFrameCnt*2)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error setting the stop threshold.");
|
||
|
|
||
|
if((err = snd_pcm_sw_params_set_avail_min(pcmH,swParams,periodFrameCnt)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error setting the avail. min. setting.");
|
||
|
|
||
|
if((err = snd_pcm_sw_params_set_tstamp_mode(pcmH,swParams,SND_PCM_TSTAMP_MMAP)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error setting the time samp mode.");
|
||
|
|
||
|
if((err = snd_pcm_sw_params(pcmH,swParams)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error applying sw params.");
|
||
|
}
|
||
|
|
||
|
// setup the callback
|
||
|
if( p->asyncFl )
|
||
|
if((err = snd_async_add_pcm_handler(&drp->ahandler,pcmH,_cmApStaticAsyncHandler, drp )) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error assigning callback handler.");
|
||
|
|
||
|
// get the actual frames per cycle
|
||
|
if((err = snd_pcm_hw_params_get_period_size(hwParams,&actFpC,&dir)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Unable to get the actual period.");
|
||
|
|
||
|
// store the device handle
|
||
|
if( inputFl )
|
||
|
{
|
||
|
drp->iBits = bits;
|
||
|
drp->iSigBits = sig_bits;
|
||
|
drp->iSignFl = signFl;
|
||
|
drp->iSwapFl = swapFl;
|
||
|
drp->iPcmH = pcmH;
|
||
|
drp->iBuf = memResizeZ<device::sample_t>( drp->iBuf, actFpC * drp->iChCnt );
|
||
|
drp->iFpC = actFpC;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
drp->oBits = bits;
|
||
|
drp->oSigBits = sig_bits;
|
||
|
drp->oSignFl = signFl;
|
||
|
drp->oSwapFl = swapFl;
|
||
|
drp->oPcmH = pcmH;
|
||
|
drp->oBuf = memResizeZ<device::sample_t>( drp->oBuf, actFpC * drp->oChCnt );
|
||
|
drp->oFpC = actFpC;
|
||
|
}
|
||
|
|
||
|
if( p->asyncFl == false )
|
||
|
{
|
||
|
cwAssert( p->pollfdsCnt < p->pollfdsAllocCnt );
|
||
|
|
||
|
unsigned incrFdsCnt = 0;
|
||
|
unsigned fdsCnt = 0;
|
||
|
|
||
|
// locate the pollfd associated with this device/direction
|
||
|
unsigned j;
|
||
|
for(j=0; j<p->pollfdsCnt; j+=p->pollfdsDesc[j].fdsCnt)
|
||
|
if( p->pollfdsDesc[j].devPtr == drp && inputFl == p->pollfdsDesc[j].inputFl )
|
||
|
break;
|
||
|
|
||
|
// get the count of descriptors for this device/direction
|
||
|
fdsCnt = snd_pcm_poll_descriptors_count(pcmH);
|
||
|
|
||
|
// if the device was not found
|
||
|
if( j == p->pollfdsCnt )
|
||
|
{
|
||
|
j = p->pollfdsCnt;
|
||
|
incrFdsCnt = fdsCnt;
|
||
|
|
||
|
// if the pollfds[] needs more memroy
|
||
|
if( p->pollfdsCnt + fdsCnt > p->pollfdsAllocCnt )
|
||
|
{
|
||
|
p->pollfds = memResizeZ<struct pollfd>( p->pollfds, p->pollfdsCnt + fdsCnt );
|
||
|
p->pollfdsDesc = memResizeZ<cmApPollfdsDesc_t>( p->pollfdsDesc, p->pollfdsCnt + fdsCnt );
|
||
|
p->pollfdsAllocCnt += fdsCnt;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get the poll descriptors for this device/dir
|
||
|
if( snd_pcm_poll_descriptors(pcmH,p->pollfds + j,fdsCnt) != 1 )
|
||
|
rc = _alsaSetupError(0,inputFl,drp,"Poll descriptor assignment failed.");
|
||
|
else
|
||
|
{
|
||
|
// store the desc. record assicoated with the poll descriptor
|
||
|
p->pollfdsDesc[ j ].fdsCnt = fdsCnt;
|
||
|
p->pollfdsDesc[ j ].devPtr = drp;
|
||
|
p->pollfdsDesc[ j ].inputFl = inputFl;
|
||
|
}
|
||
|
|
||
|
p->pollfdsCnt += incrFdsCnt;
|
||
|
}
|
||
|
printf("%s %s period:%i %i buffer:%i bits:%i sig_bits:%i\n",inputFl?"in ":"out",drp->nameStr,(unsigned)periodFrameCnt,(unsigned)actFpC,(unsigned)bufferFrameCnt,bits,sig_bits);
|
||
|
|
||
|
}
|
||
|
//_dumpAlsaDevice(pcmH);
|
||
|
|
||
|
} // end if
|
||
|
|
||
|
} // end for
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
#ifdef NOTDEF
|
||
|
#define TRY(e) while(e<0){ printf("LINE:%i ALSA ERROR:%s\n",__LINE__,snd_strerror(e)); exit(EXIT_FAILURE); }
|
||
|
|
||
|
void open_device( const char* device_name, bool inputFl )
|
||
|
{
|
||
|
snd_pcm_t *pcm_handle = NULL;
|
||
|
snd_pcm_hw_params_t *hw_params;
|
||
|
|
||
|
|
||
|
snd_pcm_uframes_t bs_min,bs_max,ps_min,ps_max;
|
||
|
unsigned rt_min,rt_max,ch_min,ch_max;
|
||
|
const char* ioLabel = inputFl ? "in" : "out";
|
||
|
|
||
|
|
||
|
// Open the device
|
||
|
TRY( snd_pcm_open (&pcm_handle, device_name, inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0));
|
||
|
|
||
|
TRY( snd_pcm_hw_params_malloc (&hw_params) );
|
||
|
TRY( snd_pcm_hw_params_any (pcm_handle, hw_params));
|
||
|
|
||
|
TRY( snd_pcm_hw_params_test_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE));
|
||
|
|
||
|
// get the sample rate range
|
||
|
TRY(snd_pcm_hw_params_get_rate_min(hw_params,&rt_min,NULL));
|
||
|
TRY(snd_pcm_hw_params_get_rate_max(hw_params,&rt_max,NULL));
|
||
|
TRY(snd_pcm_hw_params_get_channels_min(hw_params,&ch_min));
|
||
|
TRY(snd_pcm_hw_params_get_channels_max(hw_params,&ch_max));
|
||
|
|
||
|
// set the basic device format - setting the format may influence the size of the possible
|
||
|
// buffer and period size
|
||
|
//TRY( snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
|
||
|
//TRY( snd_pcm_hw_params_set_format (pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE));
|
||
|
//TRY( snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &srate, NULL));
|
||
|
//TRY( snd_pcm_hw_params_set_channels(pcm_handle, hw_params, ch_cnt));
|
||
|
|
||
|
|
||
|
// get the range of possible buffer and period sizes
|
||
|
TRY(snd_pcm_hw_params_get_buffer_size_min(hw_params,&bs_min));
|
||
|
TRY(snd_pcm_hw_params_get_buffer_size_max(hw_params,&bs_max));
|
||
|
TRY(snd_pcm_hw_params_get_period_size_min(hw_params,&ps_min,NULL));
|
||
|
TRY(snd_pcm_hw_params_get_period_size_max(hw_params,&ps_max,NULL));
|
||
|
|
||
|
//printf("%s %s bs min:%u max:%u ps min:%u max:%u rate min:%u max:%u ch min:%u max:%u\n",device_name,ioLabel,bs_min,bs_max,ps_min,ps_max,rt_min,rt_max,ch_min,ch_max);
|
||
|
printf("%s %s rate min:%u max:%u ch min:%u max:%u\n",device_name,ioLabel,rt_min,rt_max,ch_min,ch_max);
|
||
|
|
||
|
snd_pcm_hw_params_free(hw_params);
|
||
|
snd_pcm_close(pcm_handle);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cw::rc_t cw::audio::device::alsa::initialize(unsigned baseApDevIdx)
|
||
|
{
|
||
|
rc_t rc = kOkRC;
|
||
|
int err;
|
||
|
int cardNum = -1;
|
||
|
|
||
|
if((rc = finalize()) != kOkRC )
|
||
|
return rc;
|
||
|
|
||
|
recdSetup();
|
||
|
|
||
|
cmApRoot_t* p = &_cmApRoot;
|
||
|
//memset(p,0,sizeof(cmApRoot_t));
|
||
|
p->asyncFl = false;
|
||
|
|
||
|
// for each sound card
|
||
|
while(1)
|
||
|
{
|
||
|
snd_ctl_t* cardH;
|
||
|
char* cardNamePtr = NULL;
|
||
|
char* cardLongNamePtr = NULL;
|
||
|
int devNum = -1;
|
||
|
int devStrN = 31;
|
||
|
char devStr[devStrN+1];
|
||
|
|
||
|
|
||
|
// get the next card handle
|
||
|
if((err = snd_card_next(&cardNum)) < 0 )
|
||
|
{
|
||
|
return _alsaError(err,"Error getting sound card handle");
|
||
|
}
|
||
|
|
||
|
// if no more card's to get
|
||
|
if( cardNum < 0 )
|
||
|
break;
|
||
|
|
||
|
// get the card short name
|
||
|
if(((err = snd_card_get_name(cardNum,&cardNamePtr)) < 0) || (cardNamePtr == NULL))
|
||
|
{
|
||
|
_alsaError(err,"Unable to get card name for card number %i",cardNum);
|
||
|
goto releaseCard;
|
||
|
}
|
||
|
|
||
|
// get the card long name
|
||
|
if((err = snd_card_get_longname(cardNum,&cardLongNamePtr)) < 0 || cardLongNamePtr == NULL )
|
||
|
{
|
||
|
_alsaError(err,"Unable to get long card name for card number %i",cardNum);
|
||
|
goto releaseCard;
|
||
|
}
|
||
|
|
||
|
// form the device name for this card
|
||
|
if(snprintf(devStr,devStrN,"hw:%i",cardNum) > devStrN )
|
||
|
{
|
||
|
_alsaError(0,"Device name is too long for buffer.");
|
||
|
goto releaseCard;
|
||
|
}
|
||
|
|
||
|
// open the card device driver
|
||
|
if((err = snd_ctl_open(&cardH, devStr, 0)) < 0 )
|
||
|
{
|
||
|
_alsaError(err,"Error opening sound card %i.",cardNum);
|
||
|
goto releaseCard;
|
||
|
}
|
||
|
|
||
|
|
||
|
// for each device on this card
|
||
|
while(1)
|
||
|
{
|
||
|
snd_pcm_info_t* info;
|
||
|
int subDevCnt = 1;
|
||
|
int i,j;
|
||
|
|
||
|
// get the next device on this card
|
||
|
if((err = snd_ctl_pcm_next_device(cardH,&devNum)) < 0 )
|
||
|
{
|
||
|
_alsaError(err,"Error gettign next device on card %i",cardNum);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if no more devices to get
|
||
|
if( devNum < 0 )
|
||
|
break;
|
||
|
|
||
|
// allocate a pcmInfo record
|
||
|
snd_pcm_info_alloca(&info);
|
||
|
memset(info, 0, snd_pcm_info_sizeof());
|
||
|
|
||
|
// set the device to query
|
||
|
snd_pcm_info_set_device(info, devNum );
|
||
|
|
||
|
for(i=0; i<subDevCnt; i++)
|
||
|
{
|
||
|
cmApDevRecd_t dr;
|
||
|
bool inputFl = false;
|
||
|
|
||
|
memset(&dr,0,sizeof(dr));
|
||
|
|
||
|
for(j=0; j<2; j++,inputFl=!inputFl)
|
||
|
{
|
||
|
snd_pcm_t* pcmH = NULL;
|
||
|
|
||
|
dr.devIdx = -1;
|
||
|
|
||
|
// set the subdevice and I/O direction to query
|
||
|
snd_pcm_info_set_subdevice(info,i);
|
||
|
snd_pcm_info_set_stream(info,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
|
||
|
|
||
|
// if this device does not use this sub-device
|
||
|
if((err = snd_ctl_pcm_info(cardH,info)) < 0 )
|
||
|
continue;
|
||
|
|
||
|
// get the count of subdevices this device uses
|
||
|
if(i == 0 )
|
||
|
subDevCnt = snd_pcm_info_get_subdevices_count(info);
|
||
|
|
||
|
// if this device has no sub-devices
|
||
|
if(subDevCnt == 0 )
|
||
|
continue;
|
||
|
|
||
|
// form the device name and desc. string
|
||
|
if(strlen(dr.nameStr) == 0)
|
||
|
snprintf(dr.nameStr,NAME_CHAR_CNT,"hw:%i,%i,%i",cardNum,devNum,i);
|
||
|
|
||
|
|
||
|
if(strlen(dr.descStr) == 0)
|
||
|
{
|
||
|
snprintf(dr.descStr,DESC_CHAR_CNT,"%s %s",cardNamePtr,snd_pcm_info_get_name(info));
|
||
|
//snprintf(dr.descStr,DESC_CHAR_CNT,"Name:%s Card:[%s] [%s] Device:%s Subdevice:%s",dr.nameStr, cardNamePtr,cardLongNamePtr,snd_pcm_info_get_id(info),snd_pcm_info_get_name(info));
|
||
|
}
|
||
|
|
||
|
// attempt to open the sub-device
|
||
|
if((err = _cmApDevOpen(&pcmH,dr.nameStr,inputFl)) < 0 )
|
||
|
_alsaSetupError(err,inputFl,&dr,"Unable to open the PCM handle");
|
||
|
else
|
||
|
{
|
||
|
snd_pcm_hw_params_t* hwParams;
|
||
|
|
||
|
// allocate the parameter record
|
||
|
snd_pcm_hw_params_alloca(&hwParams);
|
||
|
memset( hwParams,0,snd_pcm_hw_params_sizeof());
|
||
|
|
||
|
|
||
|
// load the parameter record
|
||
|
if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 )
|
||
|
_alsaSetupError(err,inputFl,&dr,"Error obtaining hw param record");
|
||
|
else
|
||
|
{
|
||
|
unsigned* chCntPtr = inputFl ? &dr.iChCnt : &dr.oChCnt;
|
||
|
unsigned rate;
|
||
|
|
||
|
snd_pcm_hw_params_get_rate_max(hwParams,&rate,NULL);
|
||
|
|
||
|
|
||
|
// extract the channel count
|
||
|
if((err = snd_pcm_hw_params_get_channels_max(hwParams, chCntPtr )) < 0 )
|
||
|
_alsaSetupError(err,inputFl,&dr,"Error getting channel count.");
|
||
|
else
|
||
|
// this device uses this subdevice in the current direction
|
||
|
dr.flags += inputFl ? kInFl : kOutFl;
|
||
|
|
||
|
//printf("%s in:%i chs:%i rate:%i\n",dr.nameStr,inputFl,*chCntPtr,rate);
|
||
|
|
||
|
}
|
||
|
|
||
|
// close the sub-device
|
||
|
snd_pcm_close(pcmH);
|
||
|
|
||
|
}
|
||
|
|
||
|
} // in/out loop
|
||
|
|
||
|
// insert the device in the device array
|
||
|
if( dr.flags != 0 )
|
||
|
_cmApDevAppend(p,&dr);
|
||
|
|
||
|
} // sub-dev loop
|
||
|
|
||
|
} // device loop
|
||
|
|
||
|
releaseCard:
|
||
|
snd_ctl_close(cardH);
|
||
|
|
||
|
} // card loop
|
||
|
|
||
|
|
||
|
if( rc == kOkRC && p->asyncFl==false )
|
||
|
{
|
||
|
p->pollfdsCnt = 0;
|
||
|
p->pollfdsAllocCnt = 2*p->devCnt;
|
||
|
p->pollfds = memAllocZ<struct pollfd>( p->pollfdsAllocCnt );
|
||
|
p->pollfdsDesc = memAllocZ<cmApPollfdsDesc_t>(p->pollfdsAllocCnt );
|
||
|
|
||
|
if((rc = thread::create(p->thH,_cmApThreadFunc,p)) != kOkRC )
|
||
|
{
|
||
|
rc = cwLogError(rc,"Thread create failed.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
|
||
|
}
|
||
|
|
||
|
cw::rc_t cw::audio::device::alsa::finalize()
|
||
|
{
|
||
|
cmApRoot_t* p = &_cmApRoot;
|
||
|
unsigned i;
|
||
|
rc_t rc = kOkRC;
|
||
|
|
||
|
if( p->asyncFl==false )
|
||
|
if((rc = thread::destroy(p->thH)) != kOkRC )
|
||
|
{
|
||
|
rc = cwLogError(rc,"Thread destroy failed.");
|
||
|
}
|
||
|
|
||
|
for(i=0; i<p->devCnt; ++i)
|
||
|
{
|
||
|
_cmApDevShutdown(p,p->devArray+i,true);
|
||
|
_cmApDevShutdown(p,p->devArray+i,false);
|
||
|
|
||
|
#ifdef IMPULSE_FN
|
||
|
if( p->devArray[i].recdIdx > 0 )
|
||
|
{
|
||
|
const char* fn = "/home/kevin/temp/recd0.txt";
|
||
|
FILE* fp = fopen(fn,"wt");
|
||
|
if( fp != NULL )
|
||
|
{
|
||
|
unsigned j;
|
||
|
for(j=0; j<p->devArray[i].recdIdx; ++j)
|
||
|
fprintf(fp,"%i\n",p->devArray[i].recdBuf[j]);
|
||
|
fclose(fp);
|
||
|
}
|
||
|
}
|
||
|
memRelease(p->devArray[i].recdBuf);
|
||
|
#endif
|
||
|
|
||
|
memRelease(p->devArray[i].iBuf);
|
||
|
memRelease(p->devArray[i].oBuf);
|
||
|
|
||
|
}
|
||
|
|
||
|
memRelease(p->pollfds);
|
||
|
memRelease(p->pollfdsDesc);
|
||
|
|
||
|
memRelease(p->devArray);
|
||
|
p->devAllocCnt = 0;
|
||
|
p->devCnt = 0;
|
||
|
|
||
|
recdFree();
|
||
|
//write_rec(2);
|
||
|
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
unsigned cw::audio::device::alsa::deviceCount()
|
||
|
{
|
||
|
return _cmApRoot.devCnt;
|
||
|
}
|
||
|
|
||
|
const char* cw::audio::device::alsa::deviceLabel( unsigned devIdx )
|
||
|
{
|
||
|
cwAssert(devIdx < deviceCount());
|
||
|
return _cmApRoot.devArray[devIdx].descStr;
|
||
|
|
||
|
}
|
||
|
|
||
|
unsigned cw::audio::device::alsa::deviceChannelCount( unsigned devIdx, bool inputFl )
|
||
|
{
|
||
|
cwAssert(devIdx < deviceCount());
|
||
|
return inputFl ? _cmApRoot.devArray[devIdx].iChCnt : _cmApRoot.devArray[devIdx].oChCnt;
|
||
|
|
||
|
}
|
||
|
|
||
|
double cw::audio::device::alsa::deviceSampleRate( unsigned devIdx )
|
||
|
{
|
||
|
cwAssert(devIdx < deviceCount());
|
||
|
return (double)_cmApRoot.devArray[devIdx].srate;
|
||
|
}
|
||
|
|
||
|
unsigned cw::audio::device::alsa::deviceFramesPerCycle( unsigned devIdx, bool inputFl )
|
||
|
{
|
||
|
cwAssert(devIdx < deviceCount());
|
||
|
return _cmApRoot.devArray[devIdx].framesPerCycle;
|
||
|
}
|
||
|
|
||
|
cw::rc_t cw::audio::device::alsa::deviceSetup( unsigned devIdx, double srate, unsigned framesPerCycle, device::cbFunc_t cbFunc, void* cbArg )
|
||
|
{
|
||
|
|
||
|
cwAssert( devIdx < deviceCount());
|
||
|
rc_t rc = kOkRC;
|
||
|
cmApRoot_t* p = &_cmApRoot;
|
||
|
cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx;
|
||
|
unsigned periodsPerBuf = kDfltPeriodsPerBuf;
|
||
|
|
||
|
if( p->asyncFl == false )
|
||
|
if((rc = thread::pause(p->thH,thread::kWaitFl | thread::kPauseFl)) != kOkRC )
|
||
|
{
|
||
|
return cwLogError(rc,"Audio thread pause failed.");
|
||
|
}
|
||
|
|
||
|
if((rc = _cmApDevSetup(drp, srate, framesPerCycle, periodsPerBuf )) == kOkRC )
|
||
|
{
|
||
|
drp->srate = srate;
|
||
|
drp->framesPerCycle = framesPerCycle;
|
||
|
drp->periodsPerBuf = periodsPerBuf;
|
||
|
drp->cbPtr = cbFunc;
|
||
|
drp->userCbPtr = cbArg;
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
cw::rc_t cw::audio::device::alsa::deviceStart( unsigned devIdx )
|
||
|
{
|
||
|
cwAssert( devIdx < deviceCount());
|
||
|
int err;
|
||
|
cmApRoot_t* p = &_cmApRoot;
|
||
|
cmApDevRecd_t* drp = p->devArray + devIdx;
|
||
|
rc_t rc = kOkRC;
|
||
|
bool inputFl = true;
|
||
|
unsigned i;
|
||
|
|
||
|
for(i=0; i<2; ++i,inputFl=!inputFl)
|
||
|
{
|
||
|
snd_pcm_t* pcmH = inputFl ? drp->iPcmH : drp->oPcmH;
|
||
|
|
||
|
if( pcmH != NULL )
|
||
|
{
|
||
|
|
||
|
snd_pcm_state_t state = snd_pcm_state(pcmH);
|
||
|
|
||
|
if( state != SND_PCM_STATE_RUNNING )
|
||
|
{
|
||
|
unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
|
||
|
unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
|
||
|
const char* ioLabel = inputFl ? "Input" : "Output";
|
||
|
|
||
|
//printf("%i %s state:%s %i %i\n",drp->devIdx, ioLabel,_pcmStateToString(snd_pcm_state(pcmH)),chCnt,frmCnt);
|
||
|
|
||
|
// preparing may not always be necessary because the earlier call to snd_pcm_hw_params()
|
||
|
// may have left the device prepared - the redundant call however doesn't seem to hurt
|
||
|
if((err= snd_pcm_prepare(pcmH)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Error preparing the %i device.",ioLabel);
|
||
|
else
|
||
|
{
|
||
|
|
||
|
recdStart(drp,inputFl);
|
||
|
|
||
|
if( inputFl == false )
|
||
|
{
|
||
|
int j;
|
||
|
for(j=0; j<1; ++j)
|
||
|
if((err = _cmApWriteBuf( drp, pcmH, NULL, chCnt, frmCnt, drp->oBits, drp->oSigBits )) < 0 )
|
||
|
{
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"Write before start failed.");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
if((err = snd_pcm_start(pcmH)) < 0 )
|
||
|
rc = _alsaSetupError(err,inputFl,drp,"'%s' start failed.",ioLabel);
|
||
|
}
|
||
|
|
||
|
// wait 500 microseconds between starting and stopping - this prevents
|
||
|
// input and output and other device callbacks from landing on top of
|
||
|
// each other - when this happens callbacks are dropped.
|
||
|
if( p->asyncFl )
|
||
|
usleep(500);
|
||
|
|
||
|
}
|
||
|
//printf("%i %s state:%s %i %i\n",drp->devIdx, ioLabel,_cmApPcmStateToString(snd_pcm_state(pcmH)),chCnt,frmCnt);
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( p->asyncFl == false )
|
||
|
{
|
||
|
rc_t rc0 = kOkRC;
|
||
|
if((rc0 = thread::unpause(p->thH)) != kOkRC )
|
||
|
rc = _alsaError(rc0,"Audio thread start failed.");
|
||
|
}
|
||
|
return rc;
|
||
|
|
||
|
}
|
||
|
|
||
|
cw::rc_t cw::audio::device::alsa::deviceStop( unsigned devIdx )
|
||
|
{
|
||
|
int err;
|
||
|
rc_t rc = kOkRC;
|
||
|
cmApRoot_t* p = &_cmApRoot;
|
||
|
cmApDevRecd_t* drp = p->devArray + devIdx;
|
||
|
|
||
|
if( drp->iPcmH != NULL )
|
||
|
if((err = snd_pcm_drop(drp->iPcmH)) < 0 )
|
||
|
rc = _alsaSetupError(err,true,drp,"Input stop failed.");
|
||
|
|
||
|
if( drp->oPcmH != NULL )
|
||
|
if((err = snd_pcm_drop(drp->oPcmH)) < 0 )
|
||
|
rc = _alsaSetupError(err,false,drp,"Output stop failed.");
|
||
|
|
||
|
if( p->asyncFl == false )
|
||
|
{
|
||
|
rc_t rc0;
|
||
|
if((rc0 = thread::pause(p->thH,thread::kPauseFl)) != kOkRC )
|
||
|
rc =_alsaError(rc0,"Audio thread pause failed.");
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
bool cw::audio::device::alsa::deviceIsStarted( unsigned devIdx )
|
||
|
{
|
||
|
cwAssert( devIdx < deviceCount());
|
||
|
|
||
|
bool iFl = false;
|
||
|
bool oFl = false;
|
||
|
const cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx;
|
||
|
|
||
|
if( drp->iPcmH != NULL )
|
||
|
iFl = snd_pcm_state(drp->iPcmH) == SND_PCM_STATE_RUNNING;
|
||
|
|
||
|
if( drp->oPcmH != NULL )
|
||
|
oFl = snd_pcm_state(drp->oPcmH) == SND_PCM_STATE_RUNNING;
|
||
|
|
||
|
return iFl || oFl;
|
||
|
}
|
||
|
|
||
|
//{ { label:alsaDevRpt }
|
||
|
//(
|
||
|
// Here's an example of generating a report of available
|
||
|
// ALSA devices.
|
||
|
//)
|
||
|
|
||
|
//[
|
||
|
void cw::audio::device::alsa::deviceReport()
|
||
|
{
|
||
|
unsigned i;
|
||
|
for(i=0; i<_cmApRoot.devCnt; i++)
|
||
|
{
|
||
|
cwLogInfo("%i : ",i);
|
||
|
_cmApDevReport(_cmApRoot.devArray + i );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
//]
|
||
|
//}
|
||
|
|
||
|
|
||
|
|