2012-10-30 03:52:39 +00:00
|
|
|
|
|
|
|
#include "cmPrefix.h"
|
|
|
|
#include "cmGlobal.h"
|
|
|
|
#include "cmRpt.h"
|
2013-12-15 23:22:24 +00:00
|
|
|
#include "cmTime.h"
|
2012-10-30 03:52:39 +00:00
|
|
|
#include "cmAudioPort.h"
|
|
|
|
#include "cmMem.h"
|
|
|
|
#include "cmTime.h"
|
|
|
|
#include "cmMallocDebug.h"
|
|
|
|
#include "cmAudioPort.h"
|
|
|
|
#include "cmAudioPortAlsa.h"
|
|
|
|
#include "cmThread.h"
|
|
|
|
|
|
|
|
#include "alsa/asoundlib.h"
|
|
|
|
#include <unistd.h> // usleep
|
|
|
|
|
|
|
|
#define NAME_CHAR_CNT (255)
|
|
|
|
#define DESC_CHAR_CNT (255)
|
|
|
|
#define INIT_DEV_CNT (5)
|
|
|
|
|
2014-06-14 20:56:05 +00:00
|
|
|
//#define IMPULSE_FN "/home/kevin/temp/recd0.txt"
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
enum { kDfltPeriodsPerBuf = 2, kPollfdsArrayCnt=2 };
|
|
|
|
|
|
|
|
enum { kInFl=0x01, kOutFl=0x02 };
|
|
|
|
|
|
|
|
struct cmApRoot_str;
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct devRecd_str
|
|
|
|
{
|
|
|
|
struct cmApRoot_str* rootPtr;
|
|
|
|
unsigned devIdx;
|
|
|
|
cmChar_t nameStr[ NAME_CHAR_CNT ];
|
|
|
|
cmChar_t 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;
|
|
|
|
|
|
|
|
unsigned iSigBits; // significant bits in each sample beginning
|
|
|
|
unsigned oSigBits; // with the most sig. bit.
|
|
|
|
|
|
|
|
|
|
|
|
cmApSample_t* iBuf; // iBuf[ iFpc * iChCnt ]
|
|
|
|
cmApSample_t* oBuf; // oBuf[ oFpc * oChCnt ]
|
|
|
|
|
2014-06-14 20:56:05 +00:00
|
|
|
unsigned oBufCnt; // count of buffers written
|
|
|
|
|
|
|
|
#ifdef IMPULSE_FN
|
|
|
|
int* recdBuf;
|
|
|
|
unsigned recdN;
|
|
|
|
unsigned recdIdx;
|
|
|
|
#endif
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
cmApCallbackPtr_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
|
|
|
|
{
|
|
|
|
cmRpt_t* rpt; //
|
|
|
|
cmApDevRecd_t* devArray; // array of device records
|
|
|
|
unsigned devCnt; // count of actual dev recds in devArray[]
|
|
|
|
unsigned devAllocCnt; // count of dev recds allocated in devArray[]
|
|
|
|
|
|
|
|
bool asyncFl; // true=use async callback false=use polling thread
|
|
|
|
|
|
|
|
cmThreadH_t thH; // polling thread
|
|
|
|
unsigned pollfdsAllocCnt; // 2*devCnt (max possible in+out handles)
|
|
|
|
struct pollfd* pollfds; // pollfds[ pollfdsAllocCnt ]
|
|
|
|
cmApPollfdsDesc_t *pollfdsDesc; // pollfdsDesc[ pollfdsAllocCnt ]
|
|
|
|
unsigned pollfdsCnt; // count of active recds in pollfds[] and pollfdsDesc[]
|
|
|
|
|
|
|
|
} cmApRoot_t;
|
|
|
|
|
|
|
|
cmApRoot_t _cmApRoot = { NULL, NULL, 0, 0 };
|
|
|
|
|
|
|
|
//===============================================================================================
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
kReadErrRId,
|
|
|
|
kWriteErrRId
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#undef cmALSA_RECD
|
|
|
|
#ifdef cmALSA_RECD
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
kNotUsedRId,
|
|
|
|
kStartRId,
|
|
|
|
kCbRId,
|
|
|
|
kSysErrRId,
|
|
|
|
kAppErrRId,
|
|
|
|
kRecoverRId
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
int code;
|
|
|
|
char* label;
|
|
|
|
} recdErrMap_t;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
unsigned devIdx;
|
|
|
|
unsigned typeId;
|
|
|
|
cmTimeSpec_t t;
|
|
|
|
bool inputFl;
|
|
|
|
unsigned arg;
|
|
|
|
unsigned arg1;
|
|
|
|
} recd;
|
|
|
|
|
|
|
|
recd* recdArray = NULL;
|
|
|
|
unsigned recdCnt = 0;
|
|
|
|
unsigned recdIdx = 0;
|
|
|
|
cmTimeSpec_t recdTime;
|
|
|
|
|
|
|
|
recdErrMap_t recdSysErrMap[] =
|
|
|
|
{
|
|
|
|
{ -EPIPE, "EPIPE" },
|
|
|
|
{ -ESTRPIPE, "ESTRPIPE" },
|
|
|
|
{ -EBADFD, "EBADFD" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
recdErrMap_t recdAppErrMap[] =
|
|
|
|
{
|
|
|
|
{ kReadErrRId, "Read Error"},
|
|
|
|
{ kWriteErrRId, "Write Error"},
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
const char* _recdSysErrToLabel( int err )
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
for(i=0; recdSysErrMap[i].label != NULL; ++i)
|
|
|
|
if( recdSysErrMap[i].code == err )
|
|
|
|
return recdSysErrMap[i].label;
|
|
|
|
|
|
|
|
return "<Unknown sys error>";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* _recdAppErrToLabel( int err )
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
for(i=0; recdAppErrMap[i].label != NULL; ++i)
|
|
|
|
if( recdAppErrMap[i].code == err )
|
|
|
|
return recdAppErrMap[i].label;
|
|
|
|
|
|
|
|
return "<Unknown app error>";
|
|
|
|
}
|
|
|
|
|
|
|
|
void recdSetup()
|
|
|
|
{
|
|
|
|
recdCnt = 100;
|
|
|
|
recdIdx = 0;
|
|
|
|
clock_gettime(CLOCK_REALTIME,&recdTime);
|
|
|
|
recdArray = cmMemAllocZ(recd,recdCnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void recdPush( unsigned typeId, unsigned devIdx, bool inputFl, unsigned arg, unsigned arg1 )
|
|
|
|
{
|
|
|
|
//if( recdIdx == recdCnt )
|
|
|
|
// return;
|
|
|
|
|
|
|
|
if( recdIdx == recdCnt )
|
|
|
|
recdIdx = 0;
|
|
|
|
|
|
|
|
recd* r = recdArray + recdIdx;
|
|
|
|
r->typeId = typeId;
|
|
|
|
r->devIdx = devIdx;
|
|
|
|
r->inputFl = inputFl;
|
|
|
|
r->arg = arg;
|
|
|
|
r->arg1 = arg1;
|
|
|
|
clock_gettime(CLOCK_REALTIME,&r->t);
|
|
|
|
++recdIdx;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void recdStart( const cmApDevRecd_t* drp, bool inputFl )
|
|
|
|
{ recdPush(kStartRId,drp->devIdx,inputFl,0,0); }
|
|
|
|
|
|
|
|
void recdCb( const cmApDevRecd_t* drp, bool inputFl, unsigned frmCnt )
|
|
|
|
{ recdPush(kCbRId,drp->devIdx,inputFl, inputFl ? drp->iCbCnt : drp->oCbCnt, 0 ); }
|
|
|
|
|
|
|
|
void recdSysErr( bool inputFl, int err )
|
|
|
|
{ recdPush(kSysErrRId,cmInvalidIdx,inputFl,err,0); }
|
|
|
|
|
|
|
|
void recdAppErr( const cmApDevRecd_t* drp, bool inputFl, int app, int err )
|
|
|
|
{ recdPush(kAppErrRId,drp->devIdx,inputFl,app,err); }
|
|
|
|
|
|
|
|
void recdRecover( const cmApDevRecd_t* drp, bool inputFl, int err, int line )
|
|
|
|
{ recdPush(kRecoverRId,drp->devIdx,inputFl,err,line); }
|
|
|
|
|
|
|
|
void recdPrint()
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
cmTimeSpec_t t0 = recdTime;
|
|
|
|
unsigned j = recdIdx;
|
|
|
|
for(i=0; i<recdCnt; ++i)
|
|
|
|
{
|
|
|
|
|
|
|
|
recd* r = recdArray + j;
|
|
|
|
++j;
|
|
|
|
if( j == recdCnt )
|
|
|
|
j = 0;
|
|
|
|
|
|
|
|
double ms = cmTimeElapsedMicros(&recdTime,&r->t)/1000.0;
|
|
|
|
double dms = cmTimeElapsedMicros(&t0,&r->t)/1000.0;
|
|
|
|
|
|
|
|
t0 = r->t;
|
|
|
|
|
|
|
|
printf("%5i %8.3f %8.3f %i %s: ",i,ms,dms,r->devIdx,r->inputFl ? "in ":"out");
|
|
|
|
|
|
|
|
switch(r->typeId)
|
|
|
|
{
|
|
|
|
case kStartRId:
|
|
|
|
printf("start");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case kCbRId:
|
|
|
|
printf("callback %i",r->arg );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case kSysErrRId:
|
|
|
|
printf("sys err %s ",_recdSysErrToLabel(r->arg));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case kAppErrRId:
|
|
|
|
printf("app err %s %s",_recdAppErrToLabel(r->arg),_recdSysErrToLabel(r->arg1));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case kRecoverRId:
|
|
|
|
printf("Recover %s %i",_recdSysErrToLabel(r->arg),r->arg1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
printf("Unknown recd type id.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void recdFree()
|
|
|
|
{
|
|
|
|
recdPrint();
|
|
|
|
cmMemFree(recdArray);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
|
|
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(){}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//===================================================================================================
|
|
|
|
|
|
|
|
|
|
|
|
cmApRC_t _cmApOsError( cmApRoot_t* p, int err, const char* fmt, ... )
|
|
|
|
{
|
|
|
|
va_list vl;
|
|
|
|
va_start(vl,fmt);
|
|
|
|
int msgN = 255;
|
|
|
|
char msg[ msgN+1];
|
|
|
|
|
|
|
|
vsnprintf(msg,msgN,fmt,vl);
|
|
|
|
|
|
|
|
if( err )
|
|
|
|
cmRptPrintf(p->rpt,"%s ALSA Error:%s. ",msg,snd_strerror(err));
|
|
|
|
else
|
|
|
|
cmRptPrintf(p->rpt,"%s ",msg);
|
|
|
|
|
|
|
|
cmRptPrintf(p->rpt,"\n");
|
|
|
|
|
|
|
|
va_end(vl);
|
|
|
|
|
|
|
|
return kSysErrApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _cmApDevSetupError( cmApRoot_t* p, int err, bool inputFl, const cmApDevRecd_t* drp, const char* fmt, ... )
|
|
|
|
{
|
|
|
|
va_list vl;
|
|
|
|
int msgN = 255;
|
|
|
|
char msg[ msgN + 1];
|
|
|
|
|
|
|
|
va_start(vl,fmt);
|
|
|
|
vsnprintf(msg,msgN,fmt,vl);
|
|
|
|
_cmApOsError(p,err,"%s for %s '%s' : '%s'.",msg,inputFl ? "INPUT" : "OUTPUT", drp->nameStr, drp->descStr);
|
|
|
|
va_end(vl);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
}
|
|
|
|
return "<invalid>";
|
|
|
|
}
|
|
|
|
|
|
|
|
void _cmApDevRtReport( cmRpt_t* rpt, cmApDevRecd_t* drp )
|
|
|
|
{
|
|
|
|
cmRptPrintf(rpt,"cb i:%i o:%i err i:%i o:%i",drp->iCbCnt,drp->oCbCnt,drp->iErrCnt,drp->oErrCnt);
|
|
|
|
|
|
|
|
if( drp->iPcmH != NULL )
|
|
|
|
cmRptPrintf(rpt," state i:%s",_cmApPcmStateToString(snd_pcm_state(drp->iPcmH)));
|
|
|
|
|
|
|
|
if( drp->oPcmH != NULL )
|
|
|
|
cmRptPrintf(rpt," o:%s",_cmApPcmStateToString(snd_pcm_state(drp->oPcmH)));
|
|
|
|
|
|
|
|
cmRptPrint(rpt,"\n ");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void _cmApDevReport( cmRpt_t* rpt, cmApDevRecd_t* drp )
|
|
|
|
{
|
|
|
|
bool inputFl = true;
|
|
|
|
snd_pcm_t* pcmH;
|
|
|
|
int err;
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
cmApRoot_t* p = drp->rootPtr;
|
|
|
|
|
|
|
|
cmRptPrintf(rpt,"%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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,err,inputFl,drp,"Error getting max. period frame count.");
|
|
|
|
|
|
|
|
cmRptPrintf(rpt,"%s chs:%i - %i srate:%i - %i period:%i - %i buf:%i - %i half duplex only:%s joint duplex:%s\n",
|
|
|
|
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"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if((err = snd_pcm_close(pcmH)) < 0)
|
|
|
|
_cmApDevSetupError(p,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 = cmMemResizePZ( 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
|
|
|
|
|
2014-06-14 20:56:05 +00:00
|
|
|
#ifdef IMPULSE_FN
|
|
|
|
drp->recdN = 44100*2*2;
|
|
|
|
drp->recdBuf = cmMemAllocZ(int,drp->recdN);
|
|
|
|
drp->recdIdx = 0;
|
|
|
|
#endif
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
memcpy(p->devArray + p->devCnt, drp, sizeof(cmApDevRecd_t));
|
|
|
|
|
|
|
|
++p->devCnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmApRC_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 )
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(p,err,inputFl,drp,"Error closing device handle.");
|
|
|
|
return kSysErrApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
*pcmH = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return kOkApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 )
|
|
|
|
_cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"recover failed.");
|
|
|
|
|
|
|
|
if( inputFl )
|
|
|
|
{
|
|
|
|
if((err= snd_pcm_prepare(pcmH)) < 0 )
|
|
|
|
_cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"re-prepare failed.");
|
|
|
|
else
|
|
|
|
if((err = snd_pcm_start(pcmH)) < 0 )
|
|
|
|
_cmApDevSetupError(drp->rootPtr,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)
|
|
|
|
_cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"Recovery failed.\n");
|
|
|
|
else
|
|
|
|
if((err = snd_pcm_resume(pcmH))<0)
|
|
|
|
_cmApDevSetupError(drp->rootPtr,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 )
|
|
|
|
_cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"recover failed.");
|
|
|
|
|
|
|
|
printf("audio port impl ESTRPIPE:%c\n",dirCh);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case -EBADFD:
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(drp->rootPtr,err,inputFl,drp,"%s failed.",inputFl ? "Read" : "Write" );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
_cmApDevSetupError(drp->rootPtr,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_LAST:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( err < 0 )
|
|
|
|
_cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Returns count of frames written on success or < 0 on error;
|
|
|
|
// set smpPtr to NULL to write a buffer of silence
|
2014-06-14 20:56:05 +00:00
|
|
|
int _cmApWriteBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, const cmApSample_t* sp, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits )
|
2012-10-30 03:52:39 +00:00
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8;
|
|
|
|
unsigned smpCnt = chCnt * frmCnt;
|
|
|
|
unsigned byteCnt = bytesPerSmp * smpCnt;
|
|
|
|
const cmApSample_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:
|
|
|
|
{
|
|
|
|
int* dp = (int*)obuf;
|
|
|
|
while( sp < ep )
|
|
|
|
*dp++ = (int)(*sp++ * 0x7fffff);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 32:
|
|
|
|
{
|
|
|
|
int* dp = (int*)obuf;
|
2014-06-14 20:56:05 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
while( sp < ep )
|
|
|
|
*dp++ = (int)(*sp++ * 0x7fffffff);
|
2014-06-14 20:56:05 +00:00
|
|
|
|
|
|
|
#ifdef IMPLUSE_FN
|
|
|
|
int* tmp = (int*)obuf;
|
|
|
|
unsigned ii = 0;
|
|
|
|
if( drp->oBufCnt < 3 )
|
|
|
|
for(; ii<32; ++ii)
|
|
|
|
tmp[ii] = 0x7fffffff;
|
|
|
|
#endif
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-14 20:56:05 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
// send the bytes to the device
|
|
|
|
err = snd_pcm_writei( pcmH, obuf, frmCnt );
|
2014-06-14 20:56:05 +00:00
|
|
|
|
|
|
|
++drp->oBufCnt;
|
2012-10-30 03:52:39 +00:00
|
|
|
|
|
|
|
if( err < 0 )
|
|
|
|
{
|
|
|
|
recdAppErr(drp,false,kWriteErrRId,err);
|
|
|
|
_cmApDevSetupError(drp->rootPtr, err, false, drp, "ALSA write error" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if( err > 0 && err != frmCnt )
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(drp->rootPtr, 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
|
2014-06-14 20:56:05 +00:00
|
|
|
int _cmApReadBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, cmApSample_t* smpPtr, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits )
|
2012-10-30 03:52:39 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
_cmApDevSetupError(drp->rootPtr, err, false, drp, "ALSA read error" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if( err > 0 && err != frmCnt )
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(drp->rootPtr, 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
|
|
|
|
cmApSample_t* dp = smpPtr;
|
|
|
|
|
|
|
|
cmApSample_t* ep = dp + cmMin(smpCnt,err*chCnt);
|
|
|
|
|
|
|
|
switch(bits)
|
|
|
|
{
|
|
|
|
case 8:
|
|
|
|
{
|
|
|
|
char* sp = buf;
|
|
|
|
while(dp < ep)
|
|
|
|
*dp++ = ((cmApSample_t)*sp++) / 0x7f;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 16:
|
|
|
|
{
|
|
|
|
short* sp = (short*)buf;
|
|
|
|
while(dp < ep)
|
|
|
|
*dp++ = ((cmApSample_t)*sp++) / 0x7fff;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 24:
|
|
|
|
{
|
|
|
|
int* sp = (int*)buf;
|
|
|
|
while(dp < ep)
|
|
|
|
*dp++ = ((cmApSample_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++ = ((cmApSample_t)*sp++) / mv;
|
2014-06-14 20:56:05 +00:00
|
|
|
|
|
|
|
#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
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
{ assert(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;
|
|
|
|
cmApSample_t* b = inputFl ? drp->iBuf : drp->oBuf;
|
|
|
|
unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt;
|
|
|
|
unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC;
|
|
|
|
cmApAudioPacket_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;
|
2013-12-15 23:22:24 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
recdCb(drp,inputFl,0);
|
|
|
|
|
|
|
|
_cmApStateRecover( pcmH, drp, inputFl );
|
|
|
|
|
|
|
|
while( (avail = snd_pcm_avail_update(pcmH)) >= (snd_pcm_sframes_t)frmCnt )
|
|
|
|
{
|
2013-12-15 23:22:24 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
// Handle inpuut
|
|
|
|
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:
|
|
|
|
_cmApOsError(p,errno,"Poll fail.");
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
assert( 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;
|
|
|
|
cmApSample_t* b = inputFl ? drp->iBuf : drp->oBuf;
|
|
|
|
unsigned short revents = 0;
|
|
|
|
int err;
|
|
|
|
cmApAudioPacket_t pkt;
|
2013-12-15 23:22:24 +00:00
|
|
|
snd_pcm_uframes_t avail_frames;
|
2012-10-30 03:52:39 +00:00
|
|
|
|
|
|
|
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++;
|
|
|
|
|
2013-12-15 23:22:24 +00:00
|
|
|
// get the timestamp for this buffer
|
|
|
|
if((err = snd_pcm_htimestamp(pcmH,&avail_frames,&pkt.timeStamp)) != 0 )
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(p, 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);
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
|
|
|
|
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:
|
|
|
|
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 )
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(p, err, p->pollfdsDesc[i].inputFl, drp, "Return poll events failed.");
|
|
|
|
retFl = false;
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(revents & POLLERR)
|
|
|
|
{
|
|
|
|
_cmApDevSetupError(p, 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) )
|
|
|
|
{
|
2013-12-15 23:22:24 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
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;
|
|
|
|
*/
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool _cmApDevSetup( cmApDevRecd_t *drp, unsigned srate, unsigned framesPerCycle, unsigned periodsPerBuf )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
int dir;
|
|
|
|
unsigned i;
|
|
|
|
bool retFl = true;
|
|
|
|
bool inputFl = true;
|
|
|
|
snd_pcm_uframes_t periodFrameCnt = framesPerCycle;
|
|
|
|
snd_pcm_uframes_t bufferFrameCnt;
|
|
|
|
unsigned bits = 0;
|
|
|
|
int sig_bits = 0;
|
|
|
|
cmApRoot_t* p = drp->rootPtr;
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
if( _cmApDevShutdown(p, drp, inputFl ) != kOkApRC )
|
|
|
|
retFl = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error obtaining hw param record");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if((err = snd_pcm_hw_params_set_rate_resample(pcmH,hwParams,0)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl, drp,"Unable to disable the ALSA sample rate converter.");
|
|
|
|
|
|
|
|
|
|
|
|
if((err = snd_pcm_hw_params_set_channels(pcmH,hwParams,chCnt)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl, drp,"Unable to set channel count to: %i",chCnt);
|
|
|
|
|
|
|
|
|
|
|
|
if((err = snd_pcm_hw_params_set_rate(pcmH,hwParams,srate,0)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set access to: RW Interleaved");
|
|
|
|
|
|
|
|
// select the widest possible sample width
|
|
|
|
if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,SND_PCM_FORMAT_S32)) >= 0 )
|
|
|
|
bits = 32;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,SND_PCM_FORMAT_S24)) >= 0 )
|
|
|
|
bits = 24;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if((err = snd_pcm_hw_params_set_format(pcmH,hwParams,SND_PCM_FORMAT_S16)) >= 0 )
|
|
|
|
bits = 16;
|
|
|
|
else
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl, drp, "Unable to set format to: S16");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error obtaining sw param record.");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
if((err = snd_pcm_sw_params_set_start_threshold(pcmH,swParams, inputFl ? 0x7fffffff : periodFrameCnt)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error setting the stop threshold.");
|
|
|
|
|
|
|
|
if((err = snd_pcm_sw_params_set_avail_min(pcmH,swParams,periodFrameCnt)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error setting the avail. min. setting.");
|
|
|
|
|
2013-12-15 23:22:24 +00:00
|
|
|
if((err = snd_pcm_sw_params_set_tstamp_mode(pcmH,swParams,SND_PCM_TSTAMP_MMAP)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Error setting the time samp mode.");
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
if((err = snd_pcm_sw_params(pcmH,swParams)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Unable to get the actual period.");
|
|
|
|
|
|
|
|
// store the device handle
|
|
|
|
if( inputFl )
|
|
|
|
{
|
|
|
|
drp->iBits = bits;
|
|
|
|
drp->iSigBits = sig_bits;
|
|
|
|
drp->iPcmH = pcmH;
|
|
|
|
drp->iBuf = cmMemResizeZ( cmApSample_t, drp->iBuf, actFpC * drp->iChCnt );
|
|
|
|
drp->iFpC = actFpC;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
drp->oBits = bits;
|
|
|
|
drp->oSigBits = sig_bits;
|
|
|
|
drp->oPcmH = pcmH;
|
|
|
|
drp->oBuf = cmMemResizeZ( cmApSample_t, drp->oBuf, actFpC * drp->oChCnt );
|
|
|
|
drp->oFpC = actFpC;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( p->asyncFl == false )
|
|
|
|
{
|
|
|
|
assert( 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 = cmMemResizePZ(struct pollfd, p->pollfds, p->pollfdsCnt + fdsCnt );
|
|
|
|
p->pollfdsDesc = cmMemResizePZ(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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 retFl;
|
|
|
|
}
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
|
|
|
cmApRC_t cmApAlsaInitialize( cmRpt_t* rpt, unsigned baseApDevIdx )
|
|
|
|
{
|
|
|
|
cmApRC_t rc = kOkApRC;
|
|
|
|
int err;
|
|
|
|
int cardNum = -1;
|
|
|
|
|
|
|
|
if((rc = cmApAlsaFinalize()) != kOkApRC )
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
recdSetup();
|
|
|
|
|
|
|
|
cmApRoot_t* p = &_cmApRoot;
|
|
|
|
memset(p,0,sizeof(cmApRoot_t));
|
|
|
|
p->rpt = rpt;
|
|
|
|
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 )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,err,"Error getting sound card handle");
|
|
|
|
return kSysErrApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
{
|
|
|
|
_cmApOsError(p,err,"Unable to get card name for card number %i\n",cardNum);
|
|
|
|
goto releaseCard;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the card long name
|
|
|
|
if((err = snd_card_get_longname(cardNum,&cardLongNamePtr)) < 0 || cardLongNamePtr == NULL )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,err,"Unable to get long card name for card number %i\n",cardNum);
|
|
|
|
goto releaseCard;
|
|
|
|
}
|
|
|
|
|
|
|
|
// form the device name for this card
|
|
|
|
if(snprintf(devStr,devStrN,"hw:%i",cardNum) > devStrN )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,0,"Device name is too long for buffer.");
|
|
|
|
goto releaseCard;
|
|
|
|
}
|
|
|
|
|
|
|
|
// open the card device driver
|
|
|
|
if((err = snd_ctl_open(&cardH, devStr, 0)) < 0 )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,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 )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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 )
|
|
|
|
_cmApDevSetupError(p,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
|
|
|
|
|
2014-06-14 20:56:05 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
if( rc == kOkApRC && p->asyncFl==false )
|
|
|
|
{
|
|
|
|
p->pollfdsCnt = 0;
|
|
|
|
p->pollfdsAllocCnt = 2*p->devCnt;
|
|
|
|
p->pollfds = cmMemAllocZ(struct pollfd, p->pollfdsAllocCnt );
|
|
|
|
p->pollfdsDesc = cmMemAllocZ(cmApPollfdsDesc_t, p->pollfdsAllocCnt );
|
|
|
|
|
|
|
|
if( cmThreadCreate(&p->thH,_cmApThreadFunc,p,rpt) != kOkThRC )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,0,"Thread create failed.");
|
|
|
|
rc = kThreadFailApRC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmApRC_t cmApAlsaFinalize()
|
|
|
|
{
|
|
|
|
cmApRoot_t* p = &_cmApRoot;
|
|
|
|
int i;
|
|
|
|
cmApRC_t rc = kOkApRC;
|
|
|
|
|
|
|
|
if( p->asyncFl==false && cmThreadIsValid(p->thH) )
|
|
|
|
if( cmThreadDestroy(&p->thH) != kOkThRC )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,0,"Thread destroy failed.");
|
|
|
|
rc = kThreadFailApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(i=0; i<p->devCnt; ++i)
|
|
|
|
{
|
|
|
|
_cmApDevShutdown(p,p->devArray+i,true);
|
|
|
|
_cmApDevShutdown(p,p->devArray+i,false);
|
2014-06-14 20:56:05 +00:00
|
|
|
|
|
|
|
#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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cmMemFree(p->devArray[i].recdBuf);
|
|
|
|
#endif
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
cmMemPtrFree(&p->devArray[i].iBuf);
|
|
|
|
cmMemPtrFree(&p->devArray[i].oBuf);
|
2014-06-14 20:56:05 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cmMemPtrFree(&p->pollfds);
|
|
|
|
cmMemPtrFree(&p->pollfdsDesc);
|
|
|
|
|
|
|
|
cmMemPtrFree(&p->devArray);
|
|
|
|
p->devAllocCnt = 0;
|
|
|
|
p->devCnt = 0;
|
|
|
|
|
|
|
|
recdFree();
|
|
|
|
//write_rec(2);
|
|
|
|
|
2014-06-14 20:56:05 +00:00
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmApRC_t cmApAlsaDeviceCount()
|
|
|
|
{ return _cmApRoot.devCnt; }
|
|
|
|
|
|
|
|
const char* cmApAlsaDeviceLabel( unsigned devIdx )
|
|
|
|
{
|
|
|
|
assert(devIdx < cmApAlsaDeviceCount());
|
|
|
|
return _cmApRoot.devArray[devIdx].descStr;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned cmApAlsaDeviceChannelCount( unsigned devIdx, bool inputFl )
|
|
|
|
{
|
|
|
|
assert(devIdx < cmApAlsaDeviceCount());
|
|
|
|
return inputFl ? _cmApRoot.devArray[devIdx].iChCnt : _cmApRoot.devArray[devIdx].oChCnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
double cmApAlsaDeviceSampleRate( unsigned devIdx )
|
|
|
|
{
|
|
|
|
assert(devIdx < cmApAlsaDeviceCount());
|
|
|
|
return (double)_cmApRoot.devArray[devIdx].srate;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned cmApAlsaDeviceFramesPerCycle( unsigned devIdx, bool inputFl )
|
|
|
|
{
|
|
|
|
assert(devIdx < cmApAlsaDeviceCount());
|
|
|
|
return _cmApRoot.devArray[devIdx].framesPerCycle;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmApRC_t cmApAlsaDeviceSetup(
|
|
|
|
unsigned devIdx,
|
|
|
|
double srate,
|
|
|
|
unsigned framesPerCycle,
|
|
|
|
cmApCallbackPtr_t callbackPtr,
|
|
|
|
void* userCbPtr )
|
|
|
|
{
|
|
|
|
assert( devIdx < cmApAlsaDeviceCount());
|
|
|
|
cmApRoot_t* p = &_cmApRoot;
|
|
|
|
cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx;
|
|
|
|
unsigned periodsPerBuf = kDfltPeriodsPerBuf;
|
|
|
|
|
|
|
|
if( p->asyncFl == false )
|
|
|
|
if( cmThreadPause(p->thH,kWaitThFl | kPauseThFl) != kOkThRC )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,0,"Audio thread pause failed.");
|
|
|
|
return kThreadFailApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( _cmApDevSetup(drp, srate, framesPerCycle, periodsPerBuf ) )
|
|
|
|
{
|
|
|
|
drp->srate = srate;
|
|
|
|
drp->framesPerCycle = framesPerCycle;
|
|
|
|
drp->periodsPerBuf = periodsPerBuf;
|
|
|
|
drp->cbPtr = callbackPtr;
|
|
|
|
drp->userCbPtr = userCbPtr;
|
|
|
|
|
|
|
|
return kOkApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
return kSysErrApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cmApRC_t cmApAlsaDeviceStart( unsigned devIdx )
|
|
|
|
{
|
|
|
|
assert( devIdx < cmApAlsaDeviceCount());
|
|
|
|
int err;
|
|
|
|
cmApRoot_t* p = &_cmApRoot;
|
|
|
|
cmApDevRecd_t* drp = p->devArray + devIdx;
|
|
|
|
bool retFl = true;
|
|
|
|
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 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
{
|
|
|
|
retFl = _cmApDevSetupError(p,err,inputFl,drp,"Write before start failed.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
if((err = snd_pcm_start(pcmH)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,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 )
|
|
|
|
if( cmThreadPause(p->thH,0) != kOkThRC )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,0,"Audio thread start failed.");
|
|
|
|
retFl = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return retFl ? kOkApRC : kSysErrApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmApRC_t cmApAlsaDeviceStop( unsigned devIdx )
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
bool retFl = true;
|
|
|
|
cmApRoot_t* p = &_cmApRoot;
|
|
|
|
cmApDevRecd_t* drp = p->devArray + devIdx;
|
|
|
|
|
|
|
|
if( drp->iPcmH != NULL )
|
|
|
|
if((err = snd_pcm_drop(drp->iPcmH)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,true,drp,"Input stop failed.");
|
|
|
|
|
|
|
|
if( drp->oPcmH != NULL )
|
|
|
|
if((err = snd_pcm_drop(drp->oPcmH)) < 0 )
|
|
|
|
retFl = _cmApDevSetupError(p,err,false,drp,"Output stop failed.");
|
|
|
|
|
2013-12-15 23:22:24 +00:00
|
|
|
if( p->asyncFl == false )
|
|
|
|
if( cmThreadPause(p->thH,kPauseThFl) != kOkThRC )
|
|
|
|
{
|
|
|
|
_cmApOsError(p,0,"Audio thread pause failed.");
|
|
|
|
retFl = false;
|
|
|
|
}
|
|
|
|
|
2012-10-30 03:52:39 +00:00
|
|
|
return retFl ? kOkApRC : kSysErrApRC;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool cmApAlsaDeviceIsStarted( unsigned devIdx )
|
|
|
|
{
|
|
|
|
assert( devIdx < cmApAlsaDeviceCount());
|
|
|
|
|
|
|
|
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 cmApAlsaDeviceReport( cmRpt_t* rpt )
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
for(i=0; i<_cmApRoot.devCnt; i++)
|
|
|
|
{
|
|
|
|
cmRptPrintf(rpt,"%i : ",i);
|
|
|
|
_cmApDevReport(rpt,_cmApRoot.devArray + i );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//]
|
|
|
|
//}
|
|
|
|
|
|
|
|
void cmApAlsaDeviceRtReport( cmRpt_t* rpt, unsigned devIdx )
|
|
|
|
{
|
|
|
|
_cmApDevRtReport(rpt, _cmApRoot.devArray + devIdx );
|
|
|
|
|
|
|
|
}
|