summaryrefslogtreecommitdiffstats
path: root/plugins/alsa/alsa-api-streams.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/alsa/alsa-api-streams.c')
-rw-r--r--plugins/alsa/alsa-api-streams.c184
1 files changed, 110 insertions, 74 deletions
diff --git a/plugins/alsa/alsa-api-streams.c b/plugins/alsa/alsa-api-streams.c
index d101a5a..56d3cf8 100644
--- a/plugins/alsa/alsa-api-streams.c
+++ b/plugins/alsa/alsa-api-streams.c
@@ -31,16 +31,30 @@ extern Lua2cWrapperT Lua2cWrap;
typedef struct {
const char* verb;
- AlsaSndStreamT *streams;
+ AlsaLoopStreamT *stream;
SoftMixerHandleT *mixer;
+ AlsaVolRampT *ramp;
} apiHandleT;
-static void StreamApiVerbCB(AFB_ReqT request) {
+STATIC AlsaVolRampT* RampGetByUid(CtlSourceT *source, AlsaVolRampT *ramps, const char *uid) {
+ AlsaVolRampT *ramp = NULL;
+
+ // Loop on every Registryed zone pcm and extract (cardid) from (uid)
+ for (int idx = 0; ramps[idx].uid != NULL; idx++) {
+ if (!strcasecmp(ramps[idx].uid, uid)) {
+ ramp = &ramps[idx];
+ return ramp;
+ }
+ }
+ return NULL;
+}
+
+STATIC void StreamApiVerbCB(AFB_ReqT request) {
int error, doClose = 0, doQuiet = 0, doToggle = 0, doMute = -1;
long mute, volume;
- json_object *doVolume, *responseJ, *argsJ = afb_request_json(request);
+ json_object *responseJ, *volumeJ = NULL, *rampJ = NULL, *argsJ = afb_request_json(request);
apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request);
- snd_ctl_t *ctlDev=NULL;
+ snd_ctl_t *ctlDev = NULL;
CtlSourceT *source = alloca(sizeof (CtlSourceT));
source->uid = handle->verb;
@@ -48,21 +62,22 @@ static void StreamApiVerbCB(AFB_ReqT request) {
source->request = NULL;
source->context = NULL;
- error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?o !}"
+ error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?o,s?o !}"
, "quiet", &doQuiet
, "close", &doClose
, "mute", &doMute
, "toggle", &doToggle
- , "volume", &doVolume
+ , "volume", &volumeJ
+ , "ramp", &rampJ
);
if (error) {
AFB_ReqFailF(request, "StreamApiVerbCB", "Missing 'close|mute|volume|quiet' args=%s", json_object_get_string(argsJ));
goto OnErrorExit;
}
- ctlDev = AlsaCtlOpenCtl(source, handle->mixer->loop->cardid);
+ ctlDev = AlsaCtlOpenCtl(source, handle->mixer->frontend->cardid);
if (!ctlDev) {
- AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->loop->cardid);
+ AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->frontend->cardid);
goto OnErrorExit;
}
@@ -72,24 +87,24 @@ static void StreamApiVerbCB(AFB_ReqT request) {
}
if (doToggle) {
- error += AlsaCtlNumidGetLong(source, ctlDev, handle->streams->mute, &mute);
- error += AlsaCtlNumidSetLong(source, ctlDev, handle->streams->mute, !mute);
+ error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute);
+ error += AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute);
}
- if (doVolume) {
+ if (volumeJ) {
long curvol, newvol;
const char*volString;
- error = AlsaCtlNumidGetLong(source, ctlDev, handle->streams->volume, &curvol);
+ error = AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &curvol);
if (error) {
- AFB_ReqFailF(request, "invalid-numid", "Fail to set volume numid=%d value=%ld", handle->streams->volume, volume);
+ AFB_ReqFailF(request, "invalid-numid", "Fail to set volume numid=%d value=%ld", handle->stream->volume, volume);
goto OnErrorExit;
}
- switch (json_object_get_type(doVolume)) {
+ switch (json_object_get_type(volumeJ)) {
case json_type_string:
- volString = json_object_get_string(doVolume);
+ volString = json_object_get_string(volumeJ);
switch (volString[0]) {
case '+':
sscanf(&volString[1], "%ld", &newvol);
@@ -101,30 +116,38 @@ static void StreamApiVerbCB(AFB_ReqT request) {
newvol = curvol - newvol;
break;
default:
- AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(doVolume));
+ AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(volumeJ));
goto OnErrorExit;
}
break;
case json_type_int:
- newvol = json_object_get_int(doVolume);
+ newvol = json_object_get_int(volumeJ);
break;
default:
- AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(doVolume));
+ AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(volumeJ));
goto OnErrorExit;
}
- error = AlsaCtlNumidSetLong(source, ctlDev, handle->streams->volume, newvol);
+ error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->volume, newvol);
+ if (error) {
+ AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->stream->volume, newvol);
+ goto OnErrorExit;
+ }
+ }
+
+ if (rampJ) {
+ error = AlsaVolRampApply(source, handle->mixer->frontend, handle->stream, handle->ramp, rampJ);
if (error) {
- AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->streams->volume, newvol);
+ AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volram numid=%d value=%s", handle->stream->volume, json_object_get_string(rampJ));
goto OnErrorExit;
}
}
if (doMute != -1) {
- error = AlsaCtlNumidSetLong(source, ctlDev, handle->streams->mute, !mute);
+ error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute);
if (error) {
- AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->streams->volume, !mute);
+ AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->stream->volume, !mute);
goto OnErrorExit;
}
}
@@ -132,8 +155,8 @@ static void StreamApiVerbCB(AFB_ReqT request) {
// if not in quiet mode return effective selected control values
if (doQuiet) responseJ = NULL;
else {
- error += AlsaCtlNumidGetLong(source, ctlDev, handle->streams->volume, &volume);
- error += AlsaCtlNumidGetLong(source, ctlDev, handle->streams->mute, &mute);
+ error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &volume);
+ error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute);
if (error) {
AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to get stream numids volume=%ld mute=%ld", volume, mute);
goto OnErrorExit;
@@ -151,7 +174,7 @@ OnErrorExit:
}
-STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) {
+STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaLoopStreamT *stream) {
int error;
json_object *paramsJ = NULL;
@@ -160,13 +183,15 @@ STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStr
stream->mute = 0;
stream->info = NULL;
- error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o !}"
+ error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o,s?s !}"
, "uid", &stream->uid
, "info", &stream->info
, "zone", &stream->zone
, "volume", &stream->volume
, "mute", stream->mute
- , "params", &paramsJ);
+ , "params", &paramsJ
+ , "ramp", &stream->ramp
+ );
if (error) {
AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|[info]|zone|[volume]|[mute]|[params]' stream=%s", json_object_get_string(streamJ));
goto OnErrorExit;
@@ -195,9 +220,9 @@ OnErrorExit:
return -1;
}
-PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) {
+PUBLIC int LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) {
SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context;
- AlsaSndStreamT *sndStream;
+ AlsaLoopStreamT *loopStream;
int error;
long value;
size_t count;
@@ -205,37 +230,37 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp
assert(mixerHandle);
// assert static/global softmixer handle get requited info
- AlsaSndLoopT *ctlLoop = mixerHandle->loop;
+ AlsaSndLoopT *ctlLoop = mixerHandle->frontend;
if (!ctlLoop) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s No Loop found [should register snd_loop first]", mixerHandle->uid);
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s No Loop found [should Registry snd_loop first]", mixerHandle->uid);
goto OnErrorExit;
}
switch (json_object_get_type(argsJ)) {
case json_type_object:
count = 1;
- sndStream = calloc(count + 1, sizeof (AlsaSndStreamT));
- error = ProcessOneStream(source, argsJ, &sndStream[0]);
+ loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT));
+ error = ProcessOneStream(source, argsJ, &loopStream[0]);
if (error) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(argsJ));
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(argsJ));
goto OnErrorExit;
}
break;
case json_type_array:
count = json_object_array_length(argsJ);
- sndStream = calloc(count + 1, sizeof (AlsaSndStreamT));
+ loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT));
for (int idx = 0; idx < count; idx++) {
- json_object *sndStreamJ = json_object_array_get_idx(argsJ, idx);
- error = ProcessOneStream(source, sndStreamJ, &sndStream[idx]);
+ json_object *loopStreamJ = json_object_array_get_idx(argsJ, idx);
+ error = ProcessOneStream(source, loopStreamJ, &loopStream[idx]);
if (error) {
- AFB_ApiError(source->api, "sndstreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(sndStreamJ));
+ AFB_ApiError(source->api, "loopstreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(loopStreamJ));
goto OnErrorExit;
}
}
break;
default:
- AFB_ApiError(source->api, "SndStreams: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ));
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ));
goto OnErrorExit;
}
@@ -243,14 +268,14 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp
// return stream data to application as a json array
*responseJ = json_object_new_array();
- for (int idx = 0; sndStream[idx].uid != NULL; idx++) {
+ for (int idx = 0; loopStream[idx].uid != NULL; idx++) {
json_object *streamJ, *paramsJ;
// Search for a free loop capture device
- AFB_ApiNotice(source->api, "SndStreams: mixer=%s stream=%s Start", mixerHandle->uid, (char*) sndStream[idx].uid);
+ AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s Start", mixerHandle->uid, (char*) loopStream[idx].uid);
ctlLoop->scount--;
if (ctlLoop->scount < 0) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixerHandle->uid, sndStream[idx].uid, ctlLoop->uid);
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixerHandle->uid, loopStream[idx].uid, ctlLoop->uid);
goto OnErrorExit;
}
@@ -266,58 +291,58 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp
error = AlsaPcmConf(source, captureDev, &playbackDev->params);
if (error) goto OnErrorExit;
- // Register capture PCM for active/pause event
+ // Registry capture PCM for active/pause event
if (captureDev->numid) {
- error = AlsaCtlRegister(source, captureDev, captureDev->numid);
+ error = AlsaCtlRegister(source, mixerHandle, captureDev, FONTEND_NUMID_RUN, captureDev->numid);
if (error) goto OnErrorExit;
}
// Try to create/setup volume control.
snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, captureDev->handle);
if (!ctlDev) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s [pcm=%s] fail attache sndcard", mixerHandle->uid, captureDev->cardid);
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s [pcm=%s] fail attache sndcard", mixerHandle->uid, captureDev->cardid);
goto OnErrorExit;
}
- // create mute control and register it as pause/resume ctl)
+ // create mute control and Registry it as pause/resume ctl)
char runName[ALSA_CARDID_MAX_LEN];
- snprintf(runName, sizeof (runName), "run-%s", sndStream[idx].uid);
+ snprintf(runName, sizeof (runName), "run-%s", loopStream[idx].uid);
// create a single boolean value control for pause/resume
- int runNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, !sndStream[idx].mute);
- if (runNumid <= 0) goto OnErrorExit;
+ int pauseNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, loopStream[idx].mute);
+ if (pauseNumid <= 0) goto OnErrorExit;
- // register mute/unmute as a pause/resume control
- error = AlsaCtlRegister(source, captureDev, runNumid);
+ // Registry mute/unmute as a pause/resume control
+ error = AlsaCtlRegister(source, mixerHandle, captureDev, FONTEND_NUMID_PAUSE, pauseNumid);
if (error) goto OnErrorExit;
// create stream and delay pcm openning until vol control is created
char volName[ALSA_CARDID_MAX_LEN];
- snprintf(volName, sizeof (volName), "vol-%s", sndStream[idx].uid);
- AlsaPcmInfoT *streamPcm = AlsaCreateSoftvol(source, &sndStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0);
+ snprintf(volName, sizeof (volName), "vol-%s", loopStream[idx].uid);
+ AlsaPcmInfoT *streamPcm = AlsaCreateSoftvol(source, &loopStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0);
if (!streamPcm) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to create stream", mixerHandle->uid, sndStream[idx].uid);
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create stream", mixerHandle->uid, loopStream[idx].uid);
goto OnErrorExit;
}
// create volume control before softvol pcm is opened
- int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, sndStream[idx].volume);
+ int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, loopStream[idx].volume);
if (volNumid <= 0) goto OnErrorExit;
// **** Fulup (would need some help to get automatic rate converter to work).
// // add a rate converter plugin to match stream params config
// char rateName[ALSA_CARDID_MAX_LEN];
- // snprintf(rateName, sizeof (rateName), "rate-%s", sndStream[idx].uid);
+ // snprintf(rateName, sizeof (rateName), "rate-%s", loopStream[idx].uid);
// AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1);
// if (!ratePcm) {
- // AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to create rate converter", sndStream[idx].uid);
+ // AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create rate converter", loopStream[idx].uid);
// goto OnErrorExit;
// }
// everything is not ready to open capture pcm
- error = snd_pcm_open(&streamPcm->handle, sndStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ error = snd_pcm_open(&streamPcm->handle, loopStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (error) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to open capture", mixerHandle->uid, sndStream[idx].uid);
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to open capture", mixerHandle->uid, loopStream[idx].uid);
goto OnErrorExit;
}
@@ -325,18 +350,20 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp
captureDev->ccount = streamPcm->ccount;
streamPcm->params.channels = streamPcm->ccount;
- // start stream pcm copy (at this both capture & sink pcm should be open)
- error = AlsaPcmCopy(source, captureDev, streamPcm, &streamPcm->params);
+ // start stream pcm copy (at this both capture & sink pcm should be open, we use output params to configure both in+outPCM)
+ error = AlsaPcmCopy(source, &loopStream[idx], captureDev, streamPcm, &streamPcm->params);
if (error) goto OnErrorExit;
+ error = AlsaCtlRegister(source, mixerHandle, captureDev, FONTEND_NUMID_IGNORE, volNumid);
+ if (error) goto OnErrorExit;
+
// retrieve active/pause control and set PCM status accordingly
error = AlsaCtlNumidGetLong(source, ctlDev, captureDev->numid, &value);
if (error) goto OnErrorExit;
// toggle pause/resume (should be done after pcm_start)
if ((error = snd_pcm_pause(captureDev->handle, !value)) < 0) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s [capture=%s] fail to pause error=%s", mixerHandle->uid, captureDev->cardid, snd_strerror(error));
- goto OnErrorExit;
+ AFB_ApiWarning(source->api, "LoopStreams: mixer=%s [capture=%s] fail to pause error=%s", mixerHandle->uid, captureDev->cardid, snd_strerror(error));
}
// prepare response for application
@@ -344,45 +371,54 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp
error = AlsaByPathDevid(source, playbackDev);
error += wrap_json_pack(&paramsJ, "{si si si si}", "rate", streamPcm->params.rate, "channels", streamPcm->params.channels, "format", streamPcm->params.format, "access", streamPcm->params.access);
- error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", runNumid, "params", paramsJ);
+ error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", pauseNumid, "params", paramsJ);
error += json_object_array_add(*responseJ, streamJ);
if (error) {
- AFB_ApiError(source->api, "SndStreams: mixer=%s stream=%s fail to prepare response", mixerHandle->uid, captureDev->cardid);
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s fail to prepare response", mixerHandle->uid, captureDev->cardid);
goto OnErrorExit;
}
// create a dedicated verb for this stream compose of mixeruid/streamuid
apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT));
char apiVerb[128];
- error = snprintf(apiVerb, sizeof (apiVerb), "%s/%s", mixerHandle->uid, sndStream[idx].uid);
+ error = snprintf(apiVerb, sizeof (apiVerb), "%s/%s", mixerHandle->uid, loopStream[idx].uid);
if (error == sizeof (apiVerb)) {
- AFB_ApiError(source->api, "SndStreams mixer=%s fail to register Stream API too long %s/%s", mixerHandle->uid, mixerHandle->uid, sndStream[idx].uid);
+ AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixerHandle->uid, mixerHandle->uid, loopStream[idx].uid);
return -1;
}
+ // if set get stream attached volramp
+ if (loopStream->ramp) {
+ apiHandle->ramp = RampGetByUid(source, ctlLoop->ramps, loopStream->ramp);
+ if (!apiHandle->ramp) {
+ AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to find ramp=%s", mixerHandle->uid, loopStream[idx].uid, loopStream->ramp);
+ goto OnErrorExit;
+ }
+ }
+
apiHandle->mixer = mixerHandle;
- apiHandle->streams = &sndStream[idx];
+ apiHandle->stream= &loopStream[idx];
apiHandle->verb = strdup(apiVerb);
- error = afb_dynapi_add_verb(source->api, apiHandle->verb, sndStream[idx].info, StreamApiVerbCB, apiHandle, NULL, 0);
+ error = afb_dynapi_add_verb(source->api, apiHandle->verb, loopStream[idx].info, StreamApiVerbCB, apiHandle, NULL, 0);
if (error) {
- AFB_ApiError(source->api, "SndStreams mixer=%s fail to register API verb=%s", mixerHandle->uid, apiHandle->verb);
+ AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry API verb=%s", mixerHandle->uid, apiHandle->verb);
return -1;
}
// free temporary resources
snd_ctl_close(ctlDev);
- sndStream[idx].volume = volNumid;
- sndStream[idx].mute = runNumid;
+ loopStream[idx].volume = volNumid;
+ loopStream[idx].mute = pauseNumid;
// Debug Alsa Config
//AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm");
//AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle);
- AFB_ApiNotice(source->api, "SndStreams: mixer=%s stream=%s OK reponse=%s\n", mixerHandle->uid, streamPcm->uid, json_object_get_string(streamJ));
+ AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s OK reponse=%s\n", mixerHandle->uid, streamPcm->uid, json_object_get_string(streamJ));
}
// save handle for further use
- mixerHandle->streams = sndStream;
+ mixerHandle->streams = loopStream;
return 0;
OnErrorExit: