CIsimpleUserDMAfile.c

/*****************************************************************************

	CIsimpleUserDMAFile.c	Src for BitFlow CI lib file save prog (use w/NUMA)

	Oct 20,		2015	CIW/SJT

	© Copyright 2015, BitFlow, Inc. All rights reserved.

	Tabstops are 4

	$Author: steve $

	$Date: 2020/10/02 23:30:13 $

	$Id: CIsimpleUserDMAfile.c,v 1.5 2020/10/02 23:30:13 steve Exp $

*****************************************************************************/

/*==========================================================================*/
/*
**	For access to command line display.
*/
#include	<stdio.h>
#include	<stdarg.h>
#include	<string.h>
#include	<stdlib.h>
/*
**	For checking for keypress
*/
#include	<time.h>
#include	<sys/time.h>
#include	<sys/types.h>
#include	<unistd.h>
/*
**	For access to BitFlow camera interface library.
*/
#include	"BFciLib.h"
/*==========================================================================*/
static int sExitAns = 0;		/* program exit code */
static tCIp sCIp = NULL;		/* device open token */
static int sNdx = 0;			/* device index */
static int sMaxFrames = 100;	/* total frames to save */
static int sDidFrames = 0;		/* total frames handled */
static char sPrefix[1024] = "";	/* for files */
static char sCfgFN[1024] = "";	/* for special config file */
static tCIU32 sTimeoutMS = 10000;	/* prevent lockup on error */
/*--------------------------------------------------------------------------*/
#define	SHOW(x)	{ (void)printf x ; (void)fflush(stdout); }
#define	ERR(x)	{ SHOW(("ERR: ")); SHOW(x); }

static char *sArgv0 = NULL;		/* name of executable */
static void ShowHelp(void)
{
	SHOW(("%s of " __DATE__ " at " __TIME__ "\n", sArgv0));
	SHOW(("   -h           display this message and exit\n"));
	SHOW(("\n"));
	SHOW(("   -x ndx       choose available device ndx (default 0)\n"));
	SHOW(("   -m maxFrames max frames to save (default 100)\n"));
	SHOW(("   -c cfgFN     CiVFGinitialize() w/this configfile\n"));
	SHOW(("   -p prefix    for file name (default is date/time)\n"));
	SHOW(("   -t toutMsec  set timeout (%d)\n", sTimeoutMS));
	SHOW(("\n"));
	SHOW(("  CIsimpleUserDMAfile: initialize an interface and save frames\n"));
	SHOW(("\n"));
}

/*==========================================================================*/
#include	<sys/timeb.h>
static tCIDOUBLE GetTime(void)
/*
**	Return fractional seconds
*/
{
	tCIDOUBLE ans = 0.0;

#ifdef _POSIX_TIMERS

	struct timespec tp;

	(void)clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
	ans = (tCIDOUBLE) tp.tv_sec;
	ans += ((tCIDOUBLE) tp.tv_nsec) / 1000000000.0;

#else

	struct timeb tb;

	(void)ftime(&tb);
	ans = tb.millitm;
	ans /= 1000.0;
	ans += tb.time;

#endif

	return (ans);
}

/*--------------------------------------------------------------------------*/
static int DecodeArgs(int argc, char **argv)
/*
**	Parse the input arguments.
*/
{
	char *str;

	argv += 1;
	argc -= 1;					/* skip program name */

	while (argc-- > 0)
	{
		str = *argv++;
		if (str[0] != '-')
		{
			ERR(("Do not know '%s' arg\n", str));
			ShowHelp();
			sExitAns = 1;
			return (sExitAns);
		}
		switch (str[1])
		{
		case 'h':
			ShowHelp();
			return (1);
		case 'x':
			(void)sscanf(*argv, "%d", &sNdx);
			argv += 1;
			argc -= 1;
			break;
		case 'm':
			(void)sscanf(*argv, "%d", &sMaxFrames);
			argv += 1;
			argc -= 1;
			break;
		case 'c':
			(void)strcpy(sCfgFN, *argv);
			argv += 1;
			argc -= 1;
			break;
		case 'p':
			(void)strcpy(sPrefix, *argv);
			argv += 1;
			argc -= 1;
			break;
		case 't':
			(void)sscanf(*argv, "%d", &sTimeoutMS);
			argv += 1;
			argc -= 1;
			break;
		default:
			ERR(("Do not know arg '%s'\n", str));
			ShowHelp();
			sExitAns = 1;
			return (sExitAns);
		}
	}

	return (kCIEnoErr);
}

/*--------------------------------------------------------------------------*/
#include	<tiffio.h>

static void SaveOneTIFF(tCIU8 * buff, int stride, int ppr, int rpf, int bpp)
{
	static int sID = 0;
	char fn[sizeof(sPrefix) + 64];
	TIFF *tiff;
	int i;

	(void)sprintf(fn, "%s_%04d.tiff", sPrefix, sID++);

	tiff = TIFFOpen(fn, "w");
	if (NULL == tiff)
	{
		ERR(("SaveOneTIFF: cannot TIFFOpen() on '%s'\n", fn));
		return;
	}
	TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, ppr);
	TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, rpf);
	TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bpp);
	TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 1);
	TIFFSetField(tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
	TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_DEFLATE);
	TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
	for (i = 0; i < (int)rpf; i++)
	{
		TIFFWriteScanline(tiff, (tdata_t) buff, i, 0);
		buff += stride;
	}
	TIFFClose(tiff);

	SHOW(("SaveToFile: saved to '%s'\n", fn));

	return;
}

/*--------------------------------------------------------------------------*/
static void InitAndGetDataUntilKeyPress(void)
/*
**	Illustrate a simple example VFG interaction sequence.
*/
{
	tCIRC circ;
	tCIDOUBLE a = -1.0, b, c, d;
	tCIU64 totalBytes = 0, totalLines = 0;
	tCIU32 counter = sMaxFrames;
	tCIU32 nPtrs;
	tCIU8 **uPtrs = NULL;
	tCIU32 frameID;
	tCIU8 *frameP;
	tCIU32 nFrames, bitsPerPix, hROIoffset, hROIsize, vROIoffset, vROIsize, stride;
	tCIU64 allocSz;
	tCIU8 *allocPtr = NULL;
	int pagesz;
	/*
	 ** Open the ndx'th frame grabber with exclusive write permission.
	 */
	circ = CiVFGopen(sNdx, kCIBO_exclusiveWrAccess, &sCIp);
	if (kCIEnoErr != circ)
	{
		ERR(("CiVFGopen gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		return;
	}
	/*
	 ** Init the VFG with the custom file (if no custom use DIP switches)
	 */
	circ = CiVFGinitialize(sCIp, sCfgFN);
	if (kCIEnoErr != circ)
	{
		ERR(("CiVFGinitialize gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	SHOW(("VFG %d is initialized with '%s'\n", sNdx, sCfgFN));
	/*
	 ** Configure the VFG for 4 frame buffers.
	 */
	circ = CiDrvrBuffConfigure(sCIp, 4, 0, 0, 0, 0);
	if (kCIEnoErr != circ)
	{
		ERR(("CiDrvrBuffConfigure gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** Determine buffer configuration.
	 */
	circ = CiBufferInterrogate(sCIp, &nFrames, &bitsPerPix, &hROIoffset, &hROIsize, &vROIoffset, &vROIsize, &stride);
	if (kCIEnoErr != circ)
	{
		ERR(("CiBufferInterrogate gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** Release these buffers
	 */
	circ = CiDrvrBuffConfigure(sCIp, 0, 0, 0, 0, 0);
	if (kCIEnoErr != circ)
	{
		ERR(("CiDrvrBuffConfigure(2) gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** Calculate the framesize
	 */
	allocSz = stride;
	allocSz *= vROIsize;
	/*
	 ** Chunk to page boundary
	 */
	pagesz = getpagesize();
	allocSz += pagesz - 1;
	allocSz /= pagesz;
	allocSz *= pagesz;
	/*
	 ** And all frames (plus a couple spares)
	 */
	allocSz *= (sMaxFrames + 2);
	/*
	 ** Plus one page to align the pointer
	 */
	allocSz += pagesz;
	/*
	 ** Now get the memory
	 */
	allocPtr = (tCIU8 *) malloc(allocSz);
	if (NULL == allocPtr)
	{
		ERR(("cannot get %lld for %d frames\n", allocSz, nFrames));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** And hand this off to the driver
	 */
	circ = CiUserBuffConfigure(sCIp, sMaxFrames + 2, allocPtr, allocSz, 0, 0, 0, 0);
	if (kCIEnoErr != circ)
	{
		ERR(("CiUserBuffConfigure(2) gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** Get the buffer pointers for read access to buffers.
	 */
	circ = CiMapFrameBuffers(sCIp, 0, &nPtrs, &uPtrs);
	if (kCIEnoErr != circ)
	{
		ERR(("CiMapFrameBuffers gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** Reset acquisition and clear all error conditions.
	 */
	circ = CiAqSWreset(sCIp);
	if (kCIEnoErr != circ)
	{
		ERR(("CiAqSWreset gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	/*
	 ** Start acquisition of the desired number of frames.
	 */
	circ = CiAqStart(sCIp, sMaxFrames);
	if (kCIEnoErr != circ)
	{
		ERR(("CiAqStart gave '%s'\n", CiErrStr(circ)));
		sExitAns = 1;
		goto andOut;
	}
	a = GetTime();
	/*
	 ** Save the frames in a loop.
	 */
	while (1)
	{
		/*
		 ** Check to see if a frame is already available before waiting.
		 */
checkAgain:
		circ = CiGetOldestNotDeliveredFrame(sCIp, &frameID, &frameP);
		switch (circ)
		{
		case kCIEnoErr:
			/*
			 **   We have the frame.
			 */
			break;
		case kCIEnoNewData:
			/*
			 **   We need to wait for another frame.
			 */
			circ = CiWaitNextUndeliveredFrame(sCIp, sTimeoutMS);
			if (kCIEnoErr != circ)
			{
				switch (circ)
				{
				case kCIEaqAbortedErr:
					SHOW(("CiWaitNextUndeliveredFrame gave '%s'\n", CiErrStr(circ)));
					break;
				default:
					ERR(("CiWaitNextUndeliveredFrame gave '%s'\n", CiErrStr(circ)));
					sExitAns = 1;
				}
				goto andOut;
			}
			goto checkAgain;
		case kCIEaqAbortedErr:
			SHOW(("CiGetOldestNotDeliveredFrame: acqistion aborted\n"));
			goto andOut;
		default:
			ERR(("CiGetOldestNotDeliveredFrame gave '%s'\n", CiErrStr(circ)));
			sExitAns = 1;
			goto andOut;
		}

		SaveOneTIFF(frameP, stride, hROIsize, vROIsize, bitsPerPix);

		totalLines += vROIsize;
		totalBytes += stride * vROIsize;
		sDidFrames += 1;
		/*
		 **   Break out of loop if countdown hits zero
		 */
		if ((0 != counter) && (--counter == 0))
		{
			break;
		}
	}

andOut:

	/*
	 ** We MUST stop acquisition since we are about to free the DMA store
	 */
	circ = CiAqAbort(sCIp);
	if (kCIEnoErr != circ)
	{
		ERR(("CiAqAbort gave '%s'\n", CiErrStr(circ)));
	}
	/*
	 ** Unmap the frame buffers.
	 */
	if ((NULL != uPtrs) && (kCIEnoErr != (circ = CiUnmapFrameBuffers(sCIp))))
	{
		ERR(("CiUnmapFrameBuffers gave '%s'\n", CiErrStr(circ)));
	}
	/*
	 ** Close the access.
	 */
	if ((NULL != sCIp) && (kCIEnoErr != (circ = CiVFGclose(sCIp))))
	{
		ERR(("CiVFGclose gave '%s'\n", CiErrStr(circ)));
	}
	/*
	 ** Release the RAM
	 */
	if (NULL != allocPtr)
	{
		free(allocPtr);
	}
	/*
	 ** Show data rate
	 */
	c = b = GetTime() - a;
	if ((a < 0.0) || (c < 0.001))
	{
		a = 0.0;
		b = 0.0;
		c = 0.0;
		d = 0.0;
	}
	else
	{
		a = ((tCIDOUBLE) totalBytes) / c;
		b = ((tCIDOUBLE) totalLines) / c;
		d = ((tCIDOUBLE) sDidFrames) / c;
	}
	SHOW(("%d: Data rate %.1lf ln/s (%.1lf b/s) (%.1lf FPS) (%.1lf sec) after %d fr\n", sNdx, b, a, d, c, sDidFrames));

	return;
}

/*==========================================================================*/
int main(int argc, char **argv)
/*
**	Decode command line and acquire/display frames until EOF
*/
{
	static const char *sMo[] = {
		"jan", "feb", "mar", "apr", "may", "jun",
		"jul", "aug", "sep", "oct", "nov", "dec"
	};
	struct tm *p;
	time_t sec;

	sec = (time_t) GetTime();
	p = localtime(&sec);
	(void)sprintf(sPrefix, "%04d%s%02d_%02d%02d%02d", 1900 + p->tm_year, sMo[p->tm_mon], p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);

	sArgv0 = *argv;

	if (kCIEnoErr == DecodeArgs(argc, argv))
	{
		InitAndGetDataUntilKeyPress();
	}

	return (sExitAns);
}

/*==========================================================================*/
/*
	$Log: CIsimpleUserDMAfile.c,v $
	Revision 1.5  2020/10/02 23:30:13  steve
	CLOCK_MONOTONIC is not always monotonic, so prefer CLOCK_MONOTONIC_RAW.

	Revision 1.4  2020/10/02 01:17:23  steve
	ftime is deprecated. Use clock_gettime.

	Revision 1.3  2017/10/04 08:38:45  steve
	Ready for rt.

	Revision 1.2  2016/07/11 20:03:00  steve
	Gn2 xx-2Y/2YE, GPUD, DGMA

	Revision 1.1  2016/03/06 23:31:47  steve
	Support CXP_usualINit and Axion

*/