camogm.c 78.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** @file camogm.c
 * @brief Program to write captured video (and audio) to camera file system
 * using Ogg container.
 * @copyright Copyright (C) 2016 Elphel, Inc.
 *
 * @par <b>License</b>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Mikhail Karpenko's avatar
Mikhail Karpenko committed
17
 */
18
#include <unistd.h>
19 20 21 22
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
23
#include <linux/fs.h>
Mikhail Karpenko's avatar
Mikhail Karpenko committed
24
#include <sys/mman.h>
25
#include <sys/ioctl.h>
26 27
#include <sys/uio.h>
#include <sys/stat.h>
28
#include <string.h>
29
#include <getopt.h>
30
#include <ctype.h>
31
#include <elphel/ahci_cmd.h>
32 33 34 35 36

#include "camogm_ogm.h"
#include "camogm_jpeg.h"
#include "camogm_mov.h"
#include "camogm_kml.h"
37
#include "camogm_read.h"
38

39 40
/** @brief Default debug level */
#define DEFAULT_DEBUG_LVL         6
41
/** @brief JPEG trailer size in bytes */
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
#define TRAILER_SIZE              0x02
/** @brief Default segment duration in seconds */
#define DEFAULT_DURATION          600
/** @brief Default segment length in bytes */
#define DEFAULT_LENGTH            1073741824
/** @brief Behavior for the files: 0 clean buffer, 1 - save as much as possible */
#define DEFAULT_GREEDY            0
/** @brief 0 - restartf file if fps changed, 1 - ignore variable fps (and skip less frames) */
#define DEFAULT_IGNORE_FPS        0
/** @brief Maximal number of frames in file segment (each need 4* (1 + 1/frames_per_chunk) bytes for the frame index.
 * This parameter is mostrly for Quicktime */
#define DEFAULT_FRAMES            16384
/** @brief Second sparse index - for Quicktime fast forward */
#define DEFAULT_FRAMES_PER_CHUNK  10
/** @brief Use Exif */
#define DEFAULT_EXIF              1
/** @brief Bit mask indicating all 4 sensor port channels are active */
#define ALL_CHN_ACTIVE            0x0f
/** @brief Bit mask indicating all 4 sensor port channels are inactive */
#define ALL_CHN_INACTIVE          0x00
/** @brief This is a basic helper macro for processing all sensor ports at a time */
63
#define FOR_EACH_PORT(indtype, indvar) for (indtype indvar = 0; indvar < SENSOR_PORTS; indvar++)
64

65
char trailer[TRAILER_SIZE] = { 0xff, 0xd9 };
66

67 68 69
#if 0
const char *exifFileNames[] = { "/dev/exif_exif0", "/dev/exif_exif1",
                                "/dev/exif_exif2", "/dev/exif_exif3"
70 71
};

72 73
const char *headFileNames[] = { "/dev/jpeghead0", "/dev/jpeghead1",
                                "/dev/jpeghead2", "/dev/jpeghead3"
74 75
};
const char *ctlFileNames[] = {   "/dev/frameparsall0", "/dev/frameparsall1",
76
                                "/dev/frameparsall2", "/de/framepars3"
77 78
};
const char *circbufFileNames[] = {"/dev/circbuf0", "/dev/circbuf1",
79
                                "/dev/circbuf2", "/dev/circbuf3"
80
};
81 82 83 84 85 86 87 88 89 90 91 92 93 94
#else
const char *exifFileNames[] = { DEV393_PATH(DEV393_EXIF0), DEV393_PATH(DEV393_EXIF1),
                                DEV393_PATH(DEV393_EXIF2), DEV393_PATH(DEV393_EXIF3)
};

const char *headFileNames[] = { DEV393_PATH(DEV393_JPEGHEAD0), DEV393_PATH(DEV393_JPEGHEAD1),
                                DEV393_PATH(DEV393_JPEGHEAD2), DEV393_PATH(DEV393_JPEGHEAD3)
};
const char *ctlFileNames[] = {  DEV393_PATH(DEV393_FRAMEPARS0), DEV393_PATH(DEV393_FRAMEPARS1),
                                DEV393_PATH(DEV393_FRAMEPARS2), DEV393_PATH(DEV393_FRAMEPARS3)
};
const char *circbufFileNames[] = {DEV393_PATH(DEV393_CIRCBUF0), DEV393_PATH(DEV393_CIRCBUF1),
                                  DEV393_PATH(DEV393_CIRCBUF2), DEV393_PATH(DEV393_CIRCBUF3)
};
95
#endif
96 97 98 99

int lastDaemonBit[SENSOR_PORTS] = {DAEMON_BIT_CAMOGM};
struct framepars_all_t   *frameParsAll[SENSOR_PORTS];
struct framepars_t       *framePars[SENSOR_PORTS];
100 101 102
/** @brief Parameters that are not frame-related, their changes do not initiate any actions */
unsigned long            *aglobalPars[SENSOR_PORTS];
/** @brief Global structure containing current state of the running program */
103
camogm_state sstate;
104 105
/** @brief Memory mapped circular buffer arrays */
unsigned long * ccam_dma_buf[SENSOR_PORTS];
106

107 108
pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;

109 110 111 112 113 114 115 116
/**
 * @enum path_type
 * @brief Define the path type passed to a function
 * @var path_type::RAW_PATH
 * The path specified is a path to raw device buffer and it starts with /dev
 * @var path_type::FILE_PATH
 * The path specified is a regular path
 */
117 118 119 120 121
typedef enum {
	RAW_PATH,
	FILE_PATH
} path_type;

122 123 124 125 126
enum sysfs_path_type {
	TYPE_START,
	TYPE_SIZE
};

Mikhail Karpenko's avatar
Mikhail Karpenko committed
127
int debug_level;
128 129
FILE* debug_file;

130
void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num);
131 132
int camogm_start(camogm_state *state);
int camogm_stop(camogm_state *state);
133
void camogm_reset(camogm_state *state);
134
int camogm_debug(camogm_state *state, const char *fname);
135
int camogm_debug_level(int d);
136 137 138 139 140 141
void  camogm_set_segment_duration(camogm_state *state, int sd);
void  camogm_set_segment_length(camogm_state *state, int sl);
void  camogm_set_greedy(camogm_state *state, int d);
void  camogm_set_ignore_fps(camogm_state *state, int d);

void  camogm_set_save_gp(camogm_state *state, int d);
142
void  camogm_set_prefix(camogm_state *state, const char * p, path_type type);
143
void  camogm_set_exif(camogm_state *state, int d);
144 145
void  camogm_set_timescale(camogm_state *state, double d);   // set timescale, default=1.0
void  camogm_set_frames_skip(camogm_state *state, int d);    // set number of frames to skip, if negative - seconds between frames
146 147 148 149 150 151 152 153 154 155 156
void  camogm_set_format(camogm_state *state, int d);

void  camogm_kml_set_enable(camogm_state *state, int d);
void  camogm_kml_set_horHalfFov(camogm_state *state, double dd);
void  camogm_kml_set_vertHalfFov(camogm_state *state, double dd);
void  camogm_kml_set_height_mode(camogm_state *state, int d);
void  camogm_kml_set_height(camogm_state *state, double dd);
void  camogm_kml_set_period(camogm_state *state, int d);
void  camogm_kml_set_near(camogm_state *state, double dd); // distance to PhotoOverlay

int   parse_cmd(camogm_state *state, FILE* npipe);
157 158
char * getLineFromPipe(FILE* npipe);

159
int  sendImageFrame(camogm_state *state);
160

161 162 163
void  camogm_set_start_after_timestamp(camogm_state *state, double d);
void  camogm_set_max_frames(camogm_state *state, int d);
void  camogm_set_frames_per_chunk(camogm_state *state, int d);
164

165 166 167
static uint64_t get_disk_size(const char *name);
static int get_sysfs_name(const char *dev_name, char *sys_name, size_t str_sz, int type);
static int get_disk_range(const char *name, struct range *rng);
168
static int set_disk_range(const struct range *rng);
169 170 171 172 173
int open_files(camogm_state *state);
unsigned long getGPValue(unsigned int port, unsigned long GPNumber);
void setGValue(unsigned int port, unsigned long GNumber, unsigned long value);
unsigned int select_port(camogm_state *states);
inline void set_chn_state(camogm_state *s, unsigned int port, unsigned int new_state);
174
inline int is_chn_active(camogm_state *s, unsigned int port);
175
void clean_up(camogm_state *state);
176 177 178 179 180

void put_uint16(void *buf, u_int16_t val)
{
	unsigned char  *tmp;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
181
	tmp = (unsigned char*)buf;
182 183 184 185 186 187 188 189 190

	tmp[0] = val & 0xff;
	tmp[1] = (val >>= 8) & 0xff;
}

void put_uint32(void *buf, u_int32_t val)
{
	unsigned char  *tmp;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
191
	tmp = (unsigned char*)buf;
192 193 194 195 196 197 198 199 200 201 202

	tmp[0] = val & 0xff;
	tmp[1] = (val >>= 8) & 0xff;
	tmp[2] = (val >>= 8) & 0xff;
	tmp[3] = (val >>= 8) & 0xff;
}

void put_uint64(void *buf, u_int64_t val)
{
	unsigned char  *tmp;

Mikhail Karpenko's avatar
Mikhail Karpenko committed
203
	tmp = (unsigned char*)buf;
204 205 206 207 208 209 210 211 212 213 214

	tmp[0] = val & 0xff;
	tmp[1] = (val >>= 8) & 0xff;
	tmp[2] = (val >>= 8) & 0xff;
	tmp[3] = (val >>= 8) & 0xff;
	tmp[4] = (val >>= 8) & 0xff;
	tmp[5] = (val >>= 8) & 0xff;
	tmp[6] = (val >>= 8) & 0xff;
	tmp[7] = (val >>= 8) & 0xff;
}

215
/**
216
 * @brief Initialize the state of the program
217
 * @param[in]   state     pointer to #camogm_state structure containing program state
218
 * @param[in]   pipe_name pointer to command pipe name string
219
 * @param[in]   port_num  communication socket port number
220 221
 * @return      none
 */
222
void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
223 224 225 226
{
	const char sserial[] = "elp0";
	int * ipser = (int*)sserial;

227
	memset(state, 0, sizeof(camogm_state));
228 229 230 231 232 233
	camogm_set_segment_duration(state, DEFAULT_DURATION);
	camogm_set_segment_length(state, DEFAULT_LENGTH);
	camogm_set_greedy(state, DEFAULT_GREEDY);
	camogm_set_ignore_fps(state, DEFAULT_IGNORE_FPS);
	camogm_set_max_frames(state, DEFAULT_FRAMES);
	camogm_set_frames_per_chunk(state, DEFAULT_FRAMES_PER_CHUNK);
234
	camogm_reset(state);                    // sets state->buf_overruns =- 1
Mikhail Karpenko's avatar
Mikhail Karpenko committed
235 236
	state->serialno = ipser[0];
	debug_file = stderr;
237
	camogm_debug_level(DEFAULT_DEBUG_LVL);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
238
	strcpy(state->debug_name, "stderr");
239
	camogm_set_timescale(state, 1.0);
240
	camogm_set_frames_skip(state, 0);       // don't skip
241
	camogm_set_format(state, CAMOGM_FORMAT_MOV);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
242 243
	state->exif = DEFAULT_EXIF;
	state->frame_lengths = NULL;
244

245
	// reading thread has not been started yet, mutex lock is not necessary
246
	state->prog_state = STATE_STOPPED;
247
	state->rawdev.thread_state = STATE_STOPPED;
248

249
	// kml stuff
250 251 252 253
	camogm_kml_set_horHalfFov(state, 20.0);
	camogm_kml_set_vertHalfFov(state, 15.0);
	camogm_kml_set_height_mode(state, 0);
	camogm_kml_set_height(state, 10.0);
254 255
	camogm_kml_set_period(state, 2);        // 2 sec
	camogm_kml_set_near(state, 40.0);       // 40 m (distance to PhotoOverlay)
256

257
	state->pipe_name = pipe_name;
258 259
	state->rawdev.start_pos = RAWDEV_START_OFFSET;
	state->rawdev.end_pos = state->rawdev.start_pos;
260 261
	state->rawdev.curr_pos_w = state->rawdev.start_pos;
	state->rawdev.curr_pos_r = state->rawdev.start_pos;
262
	state->active_chn = ALL_CHN_ACTIVE;
263
	state->rawdev.mmap_default_size = MMAP_CHUNK_SIZE;
264
	state->sock_port = port_num;
265 266
}

267 268 269 270 271 272
/**
 * @brief Set debug output file
 * @param[in]   state   a pointer to a structure containing current state
 * @param[in]   fname   file used for debug output
 * @return      0 if the file was opened successfully and negative error code otherwise
 */
273
int camogm_debug(camogm_state *state, const char * fname)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
{
	int none = 1;

	if (fname && strlen(fname) && strcmp(fname, "none") && strcmp(fname, "null")  && strcmp(fname, "/dev/null")) none = 0;
	if (debug_file) {
		if (strcmp(state->debug_name, "stdout") && strcmp(state->debug_name, "stderr")) fclose(debug_file);
		debug_file = NULL;
		state->debug_name[0] = '\0';
	}
	if (!none) {
		if (strcmp(fname, "stdout") == 0) debug_file = stdout;
		else if (strcmp(fname, "stderr") == 0) debug_file = stderr;
		else debug_file = fopen(fname, "w+");
	}
	if (debug_file) {
		strncpy(state->debug_name, fname, sizeof(state->debug_name) - 1);
		state->debug_name[sizeof(state->debug_name) - 1] = '\0';
	}
	return 0;
293 294
}

295
/** @brief Set current debug level */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
296 297 298 299
int camogm_debug_level(int d)
{
	debug_level = d;
	return 0;
300 301
}

302 303 304 305 306
/**
 * @brief Start recording for each active sensor port
 * @param[in]   state   a pointer to a structure containing current state
 * @return      0 if the recording finished successfully and negative error code otherwise
 */
307
int camogm_start(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
308 309 310 311
{
	int timestamp_start;
	int rslt;
	int next_metadata_start, next_jpeg_len, fp;
312
	int port = state->port_num;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
313

314
	if (state->active_chn == ALL_CHN_INACTIVE) {
315 316 317 318 319 320
		D0(fprintf(debug_file, "All channels are disabled, will not start\n"));
		return CAMOGM_FRAME_OTHER;
	}
	if (state->rawdev_op && state->format != CAMOGM_FORMAT_JPEG) {
		D0(fprintf(debug_file, "Raw device write initiated, but file format is not JPEG. Will not start\n"));
		return CAMOGM_FRAME_OTHER;
321 322
	}

Mikhail Karpenko's avatar
Mikhail Karpenko committed
323 324 325
	D1(fprintf(debug_file, "Starting recording\n"));
	double dtime_stamp;
	state->frameno = 0;
326 327
	state->timescale = state->set_timescale; // current timescale, default 1
// debug
328 329
	int * ifp =      (int*)&(state->frame_params[port]);
	int * ifp_this = (int*)&(state->this_frame_params[port]);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
330 331 332 333 334 335 336
	if (state->kml_enable) camogm_init_kml();  // do nothing

	if (state->format != state->set_format) {
		state->format =  state->set_format;
		switch (state->format) {
		case CAMOGM_FORMAT_NONE: rslt = 0; break;
		case CAMOGM_FORMAT_OGM:  rslt = camogm_init_ogm(); break;
337
		case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
338 339 340
		case CAMOGM_FORMAT_MOV:  rslt = camogm_init_mov(); break;
		}
		state->formats |= 1 << (state->format);
341
		// exit on unknown formats?
Mikhail Karpenko's avatar
Mikhail Karpenko committed
342 343
	}
	state->max_frames =       state->set_max_frames;
344
	state->frames_per_chunk = state->set_frames_per_chunk;
345
	pthread_mutex_lock(&state->mutex);
346
	state->prog_state = STATE_STARTING;
347
	pthread_mutex_unlock(&state->mutex);
348
	FOR_EACH_PORT(int, chn) {
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
		if (is_chn_active(state, chn)) {
			// Check/set circbuf read pointer
			D3(fprintf(debug_file, "1: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
			if ((state->cirbuf_rp[chn] < 0) || (lseek(state->fd_circ[chn], state->cirbuf_rp[chn], SEEK_SET) < 0) || (lseek(state->fd_circ[chn], LSEEK_CIRC_VALID, SEEK_END) < 0 )) {
				D3(fprintf(debug_file, "2: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
				/* In "greedy" mode try to save as many frames from the circbuf as possible */
				state->cirbuf_rp[chn] = lseek(state->fd_circ[chn], state->greedy ? LSEEK_CIRC_SCND : LSEEK_CIRC_LAST, SEEK_END);
				if (!state->ignore_fps) {                                                                               // don't even try in ignore mode
					if (((fp = lseek(state->fd_circ[chn], LSEEK_CIRC_PREV, SEEK_END))) >= 0) state->cirbuf_rp[chn] = fp;//try to have 2 frames available for fps
				}
				state->buf_overruns[chn]++;
				// file pointer here should match state->rp; so no need to do    lseek(state->fd_circ,state->cirbuf_rp,SEEK_SET);
				state->buf_min[chn] = getGPValue(chn, G_FREECIRCBUF);

			} else {
				if (state->buf_min[chn] > getGPValue(chn, G_FREECIRCBUF)) state->buf_min[chn] = getGPValue(chn, G_FREECIRCBUF);
365

366 367 368
			}
			D3(fprintf(debug_file, "3: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
			D3(fprintf(debug_file, "4:lseek(state->fd_circ,LSEEK_CIRC_READY,SEEK_END)=%d\n", (int)lseek(state->fd_circ[chn], LSEEK_CIRC_READY, SEEK_END)));
369

370 371 372 373 374
			// is this frame ready?
			if (lseek(state->fd_circ[chn], LSEEK_CIRC_READY, SEEK_END) < 0) return -CAMOGM_FRAME_NOT_READY;  // frame pointer valid, but no frames yet
			D3(fprintf(debug_file, "5: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
			state->metadata_start = (state->cirbuf_rp[chn]) - 32;
			if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size[chn];
375

376
			// ==================================
377

378 379
			memcpy(&(state->frame_params[chn]), (unsigned long* )&ccam_dma_buf[chn][state->metadata_start >> 2], 32);
			state->jpeg_len = state->frame_params[chn].frame_length; // frame_params.frame_length are now the length of bitstream
380 381


382 383
			if (state->frame_params[chn].signffff != 0xffff) {
				D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->frame_params[chn].signffff));
384
				state->cirbuf_rp[chn] = -1;
385 386 387 388
				ifp = (int *) &state->frame_params[chn];
				D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp[chn]));
				D1(fprintf(debug_file, "%08x %08x %08x %08x %08x %08x %08x %08x\r\n", ifp[0], ifp[1], ifp[2], ifp[3], ifp[4], ifp[5], ifp[6], ifp[7]));
				return -CAMOGM_FRAME_BROKEN;
389
			}
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
			// find location of the timestamp and copy it to the frame_params structure
			// ==================================
			timestamp_start = (state->cirbuf_rp[chn]) + ((state->jpeg_len + CCAM_MMAP_META + 3) & (~0x1f)) + 32 - CCAM_MMAP_META_SEC; // magic shift - should index first byte of the time stamp
			if (timestamp_start >= state->circ_buff_size[chn]) timestamp_start -= state->circ_buff_size[chn];
			memcpy(&(state->frame_params[chn].timestamp_sec), (unsigned long* )&ccam_dma_buf[chn][timestamp_start >> 2], 8);
			// New - see if current timestamp is later than start one, if not return "CAMOGM_TOO_EARLY" reset read pointer and buffer read pointer
			if (state->start_after_timestamp > 0.0) { // don't bother if it is 0
				dtime_stamp = 0.000001 * state->frame_params[chn].timestamp_usec + state->frame_params[chn].timestamp_sec;
				if (dtime_stamp < state->start_after_timestamp) {
					state->cirbuf_rp[chn] = -1;
					D3(fprintf(debug_file, "Too early to start, %f < %f\n", dtime_stamp, state->start_after_timestamp));
					return -CAMOGM_TOO_EARLY;
				}
			}
			D3(fprintf(debug_file, "6: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));
			// see if next frame is available
			if ((lseek(state->fd_circ[chn], LSEEK_CIRC_NEXT, SEEK_END) < 0 ) ||
					// is that next frame ready?
					(((fp = lseek(state->fd_circ[chn], LSEEK_CIRC_READY, SEEK_END))) < 0)) {
				D3(fprintf(debug_file, "6a:lseek(state->fd_circ,LSEEK_CIRC_NEXT,SEEK_END)=0x%x,  fp=0x%x\n", (int)lseek(state->fd_circ[chn], LSEEK_CIRC_NEXT, SEEK_END), (int)lseek(state->fd_circ[chn], LSEEK_CIRC_READY, SEEK_END)));

				lseek(state->fd_circ[chn], state->cirbuf_rp[chn], SEEK_SET);      // just in case - restore pointer
				return -CAMOGM_FRAME_NOT_READY;                                   // frame pointer valid, but no frames yet
			}
			next_metadata_start = fp - 32;
			if (next_metadata_start < 0) next_metadata_start += state->circ_buff_size[chn];
			memcpy(&(state->this_frame_params[chn]), (unsigned long* )&ccam_dma_buf[chn][next_metadata_start >> 2], 32);
			next_jpeg_len = state->this_frame_params[chn].frame_length;  // frame_params.frame_length are now the length of bitstream
			if (state->this_frame_params[chn].signffff != 0xffff) {      // should not happen ever
				D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->this_frame_params[chn].signffff));
				ifp_this = (int *) &state->this_frame_params[chn];
				D1(fprintf(debug_file, "fp=0x%x\r\n", (int)fp));
				D1(fprintf(debug_file, "%08x %08x %08x %08x %08x %08x %08x %08x\r\n", ifp_this[0], ifp_this[1], ifp_this[2], ifp_this[3], ifp_this[4], ifp_this[5], ifp_this[6], ifp_this[7]));
				state->cirbuf_rp[chn] = -1;
				return -CAMOGM_FRAME_BROKEN;
			}
			D3(fprintf(debug_file, "7: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));

			// find location of the timestamp and copy it to the frame_params structure
			timestamp_start = fp + ((next_jpeg_len + CCAM_MMAP_META + 3) & (~0x1f)) + 32 - CCAM_MMAP_META_SEC; // magic shift - should index first byte of the time stamp
			if (timestamp_start >= state->circ_buff_size[chn]) timestamp_start -= state->circ_buff_size[chn];
			memcpy(&(state->this_frame_params[chn].timestamp_sec), (unsigned long* )&ccam_dma_buf[chn][timestamp_start >> 2], 8);
			// verify that the essential current frame params did not change, if they did - return an error (need new file header)
			if (!state->ignore_fps && ((state->frame_params[chn].width  != state->this_frame_params[chn].width) ||
					(state->frame_params[chn].height != state->this_frame_params[chn].height))) {
				// Advance frame pointer to the next (caller should try again)
				state->cirbuf_rp[chn] = fp;
				return -CAMOGM_FRAME_CHANGED; // no yet checking for the FPS
			}
			D3(fprintf(debug_file, "8: state->cirbuf_rp=0x%x\n", state->cirbuf_rp[chn]));

			// calcualte the frame period - time difference (in microseconds)
			state->frame_period[chn] = (state->this_frame_params[chn].timestamp_usec - state->frame_params[chn].timestamp_usec) +
					1000000 * (state->this_frame_params[chn].timestamp_sec  - state->frame_params[chn].timestamp_sec);

			// correct for timelapse modes:
			state->frames_skip =      state->set_frames_skip;
			if (state->frames_skip > 0) {
				state->frames_skip_left[chn] = 0;
				state->frame_period[chn] *= (state->frames_skip + 1);
			} else if (state->frames_skip < 0) {
				state->frame_period[chn] = -(state->frames_skip); // actual frame period will fluctuate to the nearest frame acquired (free running)
				state->frames_skip_left[chn] = state->frame_params[chn].timestamp_sec;
			}
			D3(fprintf(debug_file, "9: state->frame_period=0x%x\n", state->frame_period[chn]));

			state->time_unit = (ogg_int64_t)(((double)state->frame_period[chn]) * ((double)10) / ((double)state->timescale));
			state->width = state->frame_params[chn].width;
			state->height = state->frame_params[chn].height;

			// read JPEG header - it should stay the same for the whole file (restart new file if any parameters changed)
			// rebuild JPEG header:
			lseek(state->fd_head[chn], state->cirbuf_rp[chn] + 1, SEEK_END);  // +1 to avoid condition when jpeg_start==0. overloaded lseek will ignore 5 LSBs when SEEK_END
			state->head_size[chn] = lseek(state->fd_head[chn], 0, SEEK_END);  // In 8.0 the header size might change for some jp4 modes
			if (state->head_size[chn] > JPEG_HEADER_MAXSIZE) {
				D0(fprintf(debug_file, "%s:%d: Too big JPEG header (%d > %d)", __FILE__, __LINE__, state->head_size[chn], JPEG_HEADER_MAXSIZE ));
				return -2;
			}
			// and read it
			lseek(state->fd_head[chn], 0, 0);
			read(state->fd_head[chn], state->jpegHeader[chn], state->head_size[chn]);
			// Restore read pointer to the original (now there may be no frame ready there yet)
			lseek(state->fd_circ[chn], state->cirbuf_rp[chn], SEEK_SET);
473
		}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
474
	}
475

476
// here we are ready to initialize Ogm (or other) file
Mikhail Karpenko's avatar
Mikhail Karpenko committed
477 478
	switch (state->format) {
	case CAMOGM_FORMAT_NONE: rslt = 0;  break;
479 480 481
	case CAMOGM_FORMAT_OGM:  rslt = camogm_start_ogm(state);  break;
	case CAMOGM_FORMAT_JPEG: rslt = camogm_start_jpeg(state); break;
	case CAMOGM_FORMAT_MOV:  rslt = camogm_start_mov(state);  break;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
482 483 484 485 486 487
	default: rslt = 0; // do nothing
	}
	if (rslt) {
		D0(fprintf(debug_file, "camogm_start() error, rslt=0x%x\n", rslt));
		return rslt;
	}
488
	if (state->kml_enable) rslt = camogm_start_kml(state);  // will turn on state->kml_used if it can
Mikhail Karpenko's avatar
Mikhail Karpenko committed
489
	if (rslt) return rslt;
490
	pthread_mutex_lock(&state->mutex);
491
	state->prog_state = STATE_RUNNING;
492
	pthread_mutex_unlock(&state->mutex);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
493 494
	D1(fprintf(debug_file, "Started OK\n"));
	return 0;
495 496
}

497 498 499 500 501
/**
 * @brief Save a single image from circular buffer to a file.
 * @param[in]   state   a pointer to a structure containing current state
 * @return      0 if the image was successfully saved and negative error code otherwise
 */
502
int sendImageFrame(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
503 504 505 506
{
	int rslt;
	unsigned char frame_packet_type = PACKET_IS_SYNCPOINT;
	int timestamp_start;
507
	int * ifp_this = (int*)&(state->this_frame_params[state->port_num]);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
508
	int fp;
509
	int port = state->port_num;
510

511
// This is probably needed only for Quicktime (not to exceed already allocated frame index)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
512 513 514 515
	if (state->frameno >= (state->max_frames)) {
		D3(fprintf(debug_file, "sendImageFrame:1: state->frameno(0x%x) >= state->max_frames(0x%x)\n", state->frameno, state->max_frames));
		return -CAMOGM_FRAME_CHANGED;
	}
516
// Format changed?
517 518
//   D3(fprintf (debug_file,"sendImageFrame: format=%d, set_format=%d\n", state->format, state->set_format));

Mikhail Karpenko's avatar
Mikhail Karpenko committed
519 520 521 522
	if (state->format != state->set_format) {
		D3(fprintf(debug_file, "sendImageFrame:2: state->format(0x%x) != state->set_format(0x%x)\n", state->format, state->set_format));
		return -CAMOGM_FRAME_CHANGED;
	}
523
//   check if file size is exceeded (assuming fopen),-CAMOGM_FRAME_CHANGED will trigger a new segment
Mikhail Karpenko's avatar
Mikhail Karpenko committed
524 525 526 527
	if ((state->vf) && (state->segment_length >= 0) && (ftell(state->vf) > state->segment_length)) {
		D3(fprintf(debug_file, "sendImageFrame:3: segment length exceeded\n"));
		return -CAMOGM_FRAME_CHANGED;
	}
528
//same for open
Mikhail Karpenko's avatar
Mikhail Karpenko committed
529 530 531 532
	if (((state->ivf) >= 0) && (state->segment_length >= 0) && (lseek(state->ivf, 0, SEEK_CUR) > state->segment_length)) {
		D3(fprintf(debug_file, "sendImageFrame:4: segment length exceeded\n"));
		return -CAMOGM_FRAME_CHANGED;
	}
533
// check the frame pointer is valid
534
	if ((fp = lseek(state->fd_circ[port], state->cirbuf_rp[port], SEEK_SET)) < 0) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
535
		D3(fprintf(debug_file, "sendImageFrame:5: invalid frame\n"));
536

537
		return -CAMOGM_FRAME_INVALID; //it will probably be that allready
Mikhail Karpenko's avatar
Mikhail Karpenko committed
538
	}
539
// is the frame ready?
540
	if (lseek(state->fd_circ[port], LSEEK_CIRC_READY, SEEK_END) < 0) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
541
		D3(fprintf(debug_file, "?6,fp=0x%x ", fp));     //frame not ready, frame pointer seems valid, but not ready
542
		return -CAMOGM_FRAME_NOT_READY;                 // frame pointer valid, but no frames yet
Mikhail Karpenko's avatar
Mikhail Karpenko committed
543
	}
544

545 546
// process skipping frames. TODO: add - skipping time between frames (or better -  actual time period - use the nearest frame) instead of the frame number
	if ( (state->frames_skip > 0) && (state->frames_skip_left[port] > 0 )) { //skipping frames, not seconds.
547
		state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
548
//optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
549 550
		if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
		state->frames_skip_left[port]--;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
551
		D3(fprintf(debug_file, "?7 ")); //frame not ready
552
		return -CAMOGM_FRAME_NOT_READY; // the required frame is not ready
Mikhail Karpenko's avatar
Mikhail Karpenko committed
553
	}
554

555
// Get metadata
Mikhail Karpenko's avatar
Mikhail Karpenko committed
556
	D3(fprintf(debug_file, "_1_"));
557 558
	state->metadata_start = state->cirbuf_rp[port] - 32;
	if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size[port];
559
	memcpy(&(state->this_frame_params[port]), (unsigned long* )&ccam_dma_buf[state->port_num][state->metadata_start >> 2], 32);
560
	state->jpeg_len = state->this_frame_params[port].frame_length; // frame_params.frame_length are now the length of bitstream
561 562
	if (state->this_frame_params[port].signffff != 0xffff) {
		D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->this_frame_params[port].signffff));
563
		D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp[port]));
Mikhail Karpenko's avatar
Mikhail Karpenko committed
564 565 566 567 568 569
		D1(fprintf(debug_file, "%08x %08x %08x %08x %08x %08x %08x %08x\r\n", ifp_this[0], ifp_this[1], ifp_this[2], ifp_this[3], ifp_this[4], ifp_this[5], ifp_this[6], ifp_this[7]));

		D3(fprintf(debug_file, "sendImageFrame:8: frame broken\n"));
		return -CAMOGM_FRAME_BROKEN;
	}
	D3(fprintf(debug_file, "_2_"));
570 571
//   find location of the timestamp and copy it to the frame_params structure
	timestamp_start = state->cirbuf_rp[port] + ((state->jpeg_len + CCAM_MMAP_META + 3) & (~0x1f)) + 32 - CCAM_MMAP_META_SEC; // magic shift - should index first byte of the time stamp
572
	if (timestamp_start >= state->circ_buff_size[port]) timestamp_start -= state->circ_buff_size[port];
Mikhail Karpenko's avatar
Mikhail Karpenko committed
573
	D3(fprintf(debug_file, "_3_"));
574
	memcpy(&(state->this_frame_params[port].timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][timestamp_start >> 2], 8);
575
// verify that the essential current frame params did not change, if they did - return an error (need new file header)
576 577
	if (!state->ignore_fps && ((state->frame_params[port].width  != state->this_frame_params[port].width) ||
				   (state->frame_params[port].height != state->this_frame_params[port].height))) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
578
		D3(fprintf(debug_file, "sendImageFrame:9: WOI changed\n"));
579
		return -CAMOGM_FRAME_CHANGED; // not yet checking for the FPS
Mikhail Karpenko's avatar
Mikhail Karpenko committed
580
	}
581
//   check if file duration (in seconds) exceeded ,-CAMOGM_FRAME_CHANGED will trigger a new segment
Mikhail Karpenko's avatar
Mikhail Karpenko committed
582
	if ((state->segment_duration > 0) &&
583
	    ((state->this_frame_params[port].timestamp_sec - state->frame_params[port].timestamp_sec) > state->segment_duration)) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
584 585 586
		D3(fprintf(debug_file, "sendImageFrame:10: segment duration in seconds exceeded\n"));
		return -CAMOGM_FRAME_CHANGED;
	}
587
// check if (in timelapse mode)  it is too early for the frame to be stored
588
	if ((state->frames_skip < 0) && (state->frames_skip_left[port] > state->this_frame_params[port].timestamp_sec) ) {
589
		state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
590
//optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
591
		if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
592
		D3(fprintf(debug_file, "sendImageFrame:11: timelapse: frame will be skipped\n"));
593
		return -CAMOGM_FRAME_NOT_READY; // the required frame is not ready
Mikhail Karpenko's avatar
Mikhail Karpenko committed
594
	}
595 596


Mikhail Karpenko's avatar
Mikhail Karpenko committed
597 598 599
	D3(fprintf(debug_file, "_4_"));
	if (state->exif) {
		D3(fprintf(debug_file, "_5_"));
600
// update the Exif header with the current frame metadata
601
		state->exifSize[port] = lseek(state->fd_exif[port], 1, SEEK_END); // at the beginning of page 1 - position == page length
602
		if (state->exifSize[port] > 0) {
603
//state->this_frame_params.meta_index
604
			lseek(state->fd_exif[port], state->this_frame_params[port].meta_index, SEEK_END); // select meta page to use (matching frame)
605
			rslt = read(state->fd_exif[port], state->ed[port], state->exifSize[port]);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
606
			if (rslt < 0) rslt = 0;
607 608 609
			state->exifSize[port] = rslt;
		} else state->exifSize[port] = 0;
	} else state->exifSize[port] = 0;
610

Mikhail Karpenko's avatar
Mikhail Karpenko committed
611
	D3(fprintf(debug_file, "_6_"));
612

613
// prepare a packet to be sent (a lst of memory chunks)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
614 615 616
	state->chunk_index = 0;
	state->packetchunks[state->chunk_index  ].bytes = 1;
	state->packetchunks[state->chunk_index++].chunk = &frame_packet_type;
617
	if (state->exif > 0) { // insert Exif
Mikhail Karpenko's avatar
Mikhail Karpenko committed
618 619
		D3(fprintf(debug_file, "_7_"));
		state->packetchunks[state->chunk_index  ].bytes = 2;
620 621 622 623 624
		state->packetchunks[state->chunk_index++].chunk = state->jpegHeader[port];
		state->packetchunks[state->chunk_index  ].bytes = state->exifSize[port];
		state->packetchunks[state->chunk_index++].chunk = state->ed[port];
		state->packetchunks[state->chunk_index  ].bytes = state->head_size[port] - 2;
		state->packetchunks[state->chunk_index++].chunk = &(state->jpegHeader[port][2]);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
625 626
	} else {
		D3(fprintf(debug_file, "_8_"));
627 628
		state->packetchunks[state->chunk_index  ].bytes = state->head_size[port];
		state->packetchunks[state->chunk_index++].chunk = state->jpegHeader[port];
Mikhail Karpenko's avatar
Mikhail Karpenko committed
629 630
	}
	D3(fprintf(debug_file, "_9_"));
631

632 633 634
/* JPEG image data may be split in two segments (rolled over buffer end) - process both variants */
	if ((state->cirbuf_rp[port] + state->jpeg_len) > state->circ_buff_size[port]) { // two segments
/* copy from the beginning of the frame to the end of the buffer */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
635
		D3(fprintf(debug_file, "_10_"));
636 637
		state->packetchunks[state->chunk_index  ].bytes = state->circ_buff_size[port] - state->cirbuf_rp[port];
		state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp[port] >> 2];
638
/* copy from the beginning of the buffer to the end of the frame */
639
		state->packetchunks[state->chunk_index  ].bytes = state->jpeg_len - (state->circ_buff_size[port] - state->cirbuf_rp[port]);
640
		state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][0];
Mikhail Karpenko's avatar
Mikhail Karpenko committed
641 642
	} else { // single segment
		D3(fprintf(debug_file, "_11_"));
643

644
/* copy from the beginning of the frame to the end of the frame (no buffer rollovers) */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
645
		state->packetchunks[state->chunk_index  ].bytes = state->jpeg_len;
646
		state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp[port] >> 2];
Mikhail Karpenko's avatar
Mikhail Karpenko committed
647 648 649 650 651 652 653
	}
	D3(fprintf(debug_file, "_12_"));
	state->packetchunks[state->chunk_index  ].bytes = 2;
	state->packetchunks[state->chunk_index++].chunk = (unsigned char*)trailer;

	switch (state->format) {
	case CAMOGM_FORMAT_NONE: rslt = 0; break;
654 655 656
	case CAMOGM_FORMAT_OGM:  rslt = camogm_frame_ogm(state); break;
	case CAMOGM_FORMAT_JPEG: rslt = camogm_frame_jpeg(state); break;
	case CAMOGM_FORMAT_MOV:  rslt = camogm_frame_mov(state); break;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
657 658 659
	default: rslt = 0; // do nothing
	}
	if (rslt) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
660
		D3(fprintf(debug_file, "sendImageFrame:12: camogm_frame_***() returned %d\n", rslt));
Mikhail Karpenko's avatar
Mikhail Karpenko committed
661 662
		return rslt;
	}
663
	if (state->kml_used) rslt = camogm_frame_kml(state);  // will turn on state->kml_used if it can
Mikhail Karpenko's avatar
Mikhail Karpenko committed
664 665 666
	if (rslt) return rslt;

	D3(fprintf(debug_file, "_14_"));
667
// advance frame pointer
Mikhail Karpenko's avatar
Mikhail Karpenko committed
668
	state->frameno++;
669
	state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
670
// optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
671
	if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
672 673
	D3(fprintf(debug_file, "_15_\n"));
	if (state->frames_skip > 0) {
674
		state->frames_skip_left[port] = state->frames_skip;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
675
	} else if (state->frames_skip < 0) {
676
		state->frames_skip_left[port] += -(state->frames_skip);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
677 678
	}
	return 0;
679 680
}

681 682 683 684 685 686
/**
 * @brief Stop current recording. If recording was not started, this function has no effect. This function
 * calls @e camogm_end_* for the current file format.
 * @param[in]   state   a pointer to a structure containing current state
 * @return      0 if the operation was stopped successfully and negative error code otherwise
 */
687
int camogm_stop(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
688 689 690
{
	int rslt = 0;

691 692
	if (state->prog_state != STATE_RUNNING) {
		if (state->prog_state != STATE_STARTING) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
693 694
			D2(fprintf(debug_file, "Recording was not running, nothing to stop\n"));
		} else {
695
			pthread_mutex_lock(&state->mutex);
696
			state->prog_state = STATE_STOPPED;
697
			pthread_mutex_unlock(&state->mutex);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
698 699 700 701 702
			D1(fprintf(debug_file, "Dropping attempt to start\n"));
		}
		return 0;
	}
	D1(fprintf(debug_file, "Ending recording\n"));
703
	if (state->kml_used) camogm_end_kml(state);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
704 705
	switch (state->format) {
	case CAMOGM_FORMAT_NONE: rslt = 0; break;
706
	case CAMOGM_FORMAT_OGM:  rslt = camogm_end_ogm(state); break;
707
	case CAMOGM_FORMAT_JPEG: rslt = camogm_end_jpeg(state); break;
708
	case CAMOGM_FORMAT_MOV:  rslt = camogm_end_mov(state); break;
709
//    default: return 0; // do nothing
Mikhail Karpenko's avatar
Mikhail Karpenko committed
710
	}
711
// now close video file (if it is open)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
712 713 714 715
	if (state->vf) fclose(state->vf);
	state->vf = NULL;
	if (rslt) return rslt;
	state->last = 1;
716
	pthread_mutex_lock(&state->mutex);
717
	state->prog_state = STATE_STOPPED;
718
	pthread_mutex_unlock(&state->mutex);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
719
	return 0;
720 721
}

722 723 724 725 726 727
/**
 * @brief Free all file format handlers that were used. @e camogm_free_* for
 * each format used will be called.
 * @param[in]   state   a pointer to a structure containing current state
 * @return      None
 */
728
void camogm_free(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
729
{
730
	for (int f = 0; f < 31; f++) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
731 732 733 734 735 736 737 738 739 740
		if (state->formats & ( 1 << (state->format))) {
			switch (f) {
			case CAMOGM_FORMAT_NONE: break;
			case CAMOGM_FORMAT_OGM:  camogm_free_ogm(); break;
			case CAMOGM_FORMAT_JPEG: camogm_free_jpeg(); break;
			case CAMOGM_FORMAT_MOV:  camogm_free_mov(); break;
			}
		}
	}
	state->formats = 0;
741 742
}

743 744 745 746 747 748
/**
 * @brief Reset read pointer and overruns counter for each sensor port
 * @param[in]   state   a pointer to a structure containing current state
 * @return      None
 */
void camogm_reset(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
749
{
750
	FOR_EACH_PORT(int, chn) {
751 752
		state->cirbuf_rp[chn] = -1;
		state->buf_overruns[chn] = -1; // first overrun will not count
753
	}
754 755
}

756
/** @brief kml parameter setter */
757
void  camogm_kml_set_enable(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
758 759
{
	state->kml_enable = d;
760
}
761
/** @brief kml parameter setter */
762
void  camogm_kml_set_horHalfFov(camogm_state *state, double dd)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
763 764
{
	state->kml_horHalfFov = dd;
765
}
766
/** @brief kml parameter setter */
767
void  camogm_kml_set_vertHalfFov(camogm_state *state, double dd)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
768 769
{
	state->kml_vertHalfFov = dd;
770
}
771
/** @brief kml parameter setter */
772
void  camogm_kml_set_height_mode(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
773 774
{
	state->kml_height_mode = d;
775
}
776
/** @brief kml parameter setter */
777
void  camogm_kml_set_height(camogm_state *state, double dd)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
778 779
{
	state->kml_height = dd;
780
}
781
/** @brief kml parameter setter */
782
void  camogm_kml_set_period(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
783 784 785 786
{
	state->kml_period = d;
	state->kml_last_ts = 0;
	state->kml_last_uts = 0;
787
}
788
/** @brief kml parameter setter */
789
void  camogm_kml_set_near(camogm_state *state, double dd)   // distance to PhotoOverlay
Mikhail Karpenko's avatar
Mikhail Karpenko committed
790 791
{
	state->kml_near = dd;
792 793 794
}


795
void  camogm_set_segment_duration(camogm_state *state, int sd)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
796 797
{
	state->segment_duration = sd;
798
}
799
void  camogm_set_segment_length(camogm_state *state, int sl)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
800 801
{
	state->segment_length =   sl;
802
}
803
void  camogm_set_save_gp(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
804 805
{
	state->save_gp =   d;
806
}
807
void  camogm_set_exif(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
808 809
{
	state->exif =   d;
810 811
}

812
void  camogm_set_greedy(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
813 814
{
	state->greedy =   d ? 1 : 0;
815
}
816
void  camogm_set_ignore_fps(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
817 818
{
	state->ignore_fps =   d ? 1 : 0;
819 820
}

821 822 823 824 825 826 827
/**
 * @brief Set file name prefix or raw device file name.
 * @param[in]   state   a pointer to a structure containing current state
 * @param[in]   p       a pointer to the prefix string. The string is prepended to
 * the file name as is.
 * @param[in]   type    the type of prefix, can be one of #path_type
 */
828
void  camogm_set_prefix(camogm_state *state, const char * p, path_type type)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
829
{
830 831
	struct range rng = {0};

832 833 834 835 836 837 838
	if (type == FILE_PATH) {
		strncpy(state->path_prefix, p, sizeof(state->path_prefix) - 1);
		state->path_prefix[sizeof(state->path_prefix) - 1] = '\0';
	} else if (type == RAW_PATH && (strncmp(p, "/dev/", 5) == 0)) {
		strncpy(state->rawdev.rawdev_path, p, sizeof(state->rawdev.rawdev_path) - 1);
		state->rawdev.rawdev_path[sizeof(state->rawdev.rawdev_path) - 1] = '\0';
		state->rawdev.end_pos = get_disk_size(state->rawdev.rawdev_path);
839 840 841
		if (state->rawdev.end_pos == 0) {
			state->rawdev_op = 0;
			state->rawdev.end_pos = state->rawdev.start_pos;
842 843
			state->rawdev.rawdev_path[0] = '\0';
			D0(fprintf(debug_file, "ERROR: unable to initiate raw device operation\n"));
844
		} else {
845
			D0(fprintf(debug_file, "WARNING: raw device write initiated\n"));
846 847
			state->rawdev_op = 1;
		}
848 849

		if (get_disk_range(state->rawdev.rawdev_path, &rng) == 0) {
850
			set_disk_range(&rng);
851 852 853
		} else {
			D0(fprintf(debug_file, "ERROR: unable to get disk size and starting sector\n"));
		}
854
	}
855 856
}

857 858
/** @brief Set timescale @e d, default = 1,000,000 */
void  camogm_set_timescale(camogm_state *state, double d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
859 860
{
	state->set_timescale =  d;
861
	if (state->prog_state == STATE_STOPPED) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
862 863
		state->timescale = state->set_timescale;
	}
864 865
}

866
/** @brief Set the number of frames @e d to be skipped during recording. This values is common for each sensor port */
867
void  camogm_set_frames_skip(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
868 869
{
	state->set_frames_skip =  d;
870
	if (state->prog_state == STATE_STOPPED) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
871
		state->frames_skip =      state->set_frames_skip;
872
		state->frames_skip_left[state->port_num] = 0;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
873
	}
874 875
}

876 877
/** @brief Set file format @e d the images will be recorded to. This function calls corresponding @e camogm_init_*
 * function for the format selected.*/
878
void  camogm_set_format(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
879 880 881 882
{
	int rslt = 0;

	state->set_format =  d;
883
	if (state->prog_state == STATE_STOPPED) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
884 885 886 887
		state->format =  state->set_format;
		switch (state->format) {
		case CAMOGM_FORMAT_NONE: rslt = 0; break;
		case CAMOGM_FORMAT_OGM:  rslt = camogm_init_ogm(); break;
888
		case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
889 890 891 892 893 894 895
		case CAMOGM_FORMAT_MOV:  rslt = camogm_init_mov(); break;
		}
		if (rslt) {
			D0(fprintf(debug_file, "%s:%d: Error setting format to=%d\n", __FILE__, __LINE__, state->format));
		}
		state->formats |= 1 << (state->format);
	}
896
}
897 898

/** @brief Set max number of frames @e d */
899
void  camogm_set_max_frames(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
900 901
{
	state->set_max_frames =  d;
902 903
	if (state->prog_state == STATE_STOPPED)
		state->max_frames = d;
904
}
905 906

/** @brief Set the number of frames @e d recorded per chunk */
907
void  camogm_set_frames_per_chunk(camogm_state *state, int d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
908 909
{
	state->set_frames_per_chunk =  d;
910 911
	if (state->prog_state == STATE_STOPPED)
		state->frames_per_chunk = d;
912
}
913 914

/** @brief Set the time stamp @e d when recording should be started */
915
void  camogm_set_start_after_timestamp(camogm_state *state, double d)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
916 917
{
	state->start_after_timestamp =  d;
918 919
}

920 921 922 923 924 925
/**
 * @brief Print current status either as plain text or in xml format
 * @param[in]   state   to a structure containing current state
 * @param[in]   fn      a pointer to a file name which will be used for output. Can be NULL or 'stdout' for
 * output to stdout, 'stderr' for output to stderr and a file name for output to a file
 * @param[in]   xml     flag indicating that the output should be in xml format
926
 * @note access to state->rawdev.curr_pos_r is not locked in reading thread
927 928
 * @return      None
 */
929
void  camogm_status(camogm_state *state, char * fn, int xml)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
930 931
{
	int _len = 0;
932
	int _dur = 0, _udur = 0, _dur_raw, _udur_raw;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
933
	FILE* f;
934
	char *_state, *_output_format, *_using_exif, *_using_global_pointer, *_compressor_state[SENSOR_PORTS];
935
	int _b_free[SENSOR_PORTS], _b_used[SENSOR_PORTS], _b_size[SENSOR_PORTS];
936 937
	int _frames_remain[SENSOR_PORTS] = {0};
	int _sec_remain[SENSOR_PORTS] = {0};
Mikhail Karpenko's avatar
Mikhail Karpenko committed
938 939 940
	int _frames_skip = 0;
	int _sec_skip = 0;
	char *_kml_enable, *_kml_used, *_kml_height_mode;
941
	unsigned int _percent_done;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
942 943 944

	_kml_enable =      state->kml_enable ? "yes" : "no";
	_kml_used =        state->kml_used ? "yes" : "no";
945
	_kml_height_mode = state->kml_height_mode ? "GPS altitude" : "map ground level"; // 1 - actual, 0 - ground
Mikhail Karpenko's avatar
Mikhail Karpenko committed
946

947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
	for (int chn = 0; chn < SENSOR_PORTS; chn++) {
		_b_free[chn] = getGPValue(chn, G_FREECIRCBUF);
		_b_used[chn] = getGPValue(chn, G_CIRCBUFSIZE) - getGPValue(state->port_num, G_FREECIRCBUF);
		_b_size[chn] = getGPValue(chn, G_FRAME_SIZE);
		_compressor_state[chn] = (getGPValue(chn, P_COMPRESSOR_RUN) == 2) ? "running" : "stopped";
		if (state->frames_skip > 0)
			_frames_remain[chn] = state->frames_skip_left[chn];
		else if (state->frames_skip < 0)
			_sec_remain[chn] = (state->frames_skip_left[chn] - state->this_frame_params[chn].timestamp_sec);

		_dur_raw = state->this_frame_params[chn].timestamp_sec - state->frame_params[chn].timestamp_sec;
		_udur_raw = state->this_frame_params[chn].timestamp_usec - state->frame_params[chn].timestamp_usec;
		if (_udur_raw < 0) {
			_dur_raw -= 1;
			_udur_raw += 1000000;
		} else if (_udur_raw >= 1000000) {
			_dur_raw += 1;
			_udur_raw -= 1000000;
		}
		_dur += _dur_raw;
		_udur += _udur_raw;
		if (_udur > 1000000) {
			_dur += 1;
			_udur -= 1000000;
		}
	}
	if ( state->frames_skip > 0 ) {
		_frames_skip = state->frames_skip;
	} else if ( state->frames_skip < 0 ) {
		_sec_skip = -(state->frames_skip);
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
978 979 980 981 982 983 984 985 986 987

	if (!fn) f = stdout;
	else if (strcmp(fn, "stdout") == 0) f = stdout;
	else if (strcmp(fn, "stderr") == 0) f = stderr;
	else {
		if (!((f = fopen(fn, "w")))) {
			D0(fprintf(debug_file, "Error opening %s\n", fn));
			return;
		}
	}
988 989
	if (state->vf) _len = ftell(state->vf);                                 // for ogm
	else if ((state->ivf) >= 0) _len = lseek(state->ivf, 0, SEEK_CUR);      //for mov
990 991 992 993 994 995 996 997 998 999 1000 1001 1002
	switch (state->prog_state) {
	case STATE_STOPPED:
		_state = "stopped";
		break;
	case STATE_RUNNING:
		_state = "running";
		break;
	case STATE_STARTING:
		_state = "starting";
		break;
	case STATE_READING:
		_state = "reading";
		break;
1003 1004
	default:
		_state = "stopped";
1005
	}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1006 1007 1008 1009 1010 1011
	_output_format = state->format ? ((state->format == CAMOGM_FORMAT_OGM) ? "ogm" :
					  ((state->format == CAMOGM_FORMAT_JPEG) ? "jpeg" :
					   ((state->format == CAMOGM_FORMAT_MOV) ? "mov" :
					       "other"))) : "none";
	_using_exif =    state->exif ? "yes" : "no";
	_using_global_pointer = state->save_gp ? "yes" : "no";
1012 1013 1014 1015
	if (state->rawdev.curr_pos_r != 0 && state->rawdev.curr_pos_r > state->rawdev.start_pos)
		_percent_done = 100 * state->rawdev.curr_pos_r / (state->rawdev.end_pos - state->rawdev.start_pos);
	else
		_percent_done = 0;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052

	if (xml) {
		fprintf(f, "<?xml version=\"1.0\"?>\n" \
			"<camogm_state>\n" \
			"  <state>\"%s\"</state>\n" \
			"  <file_name>\"%s\"</file_name>\n" \
			"  <frame_number>%d</frame_number>\n" \
			"  <start_after_timestamp>%f</start_after_timestamp>\n"	\
			"  <file_duration>%d.%06d</file_duration>\n" \
			"  <file_length>%d</file_length>\n" \
			"  <frames_skip_left>%d</frames_skip_left>\n" \
			"  <seconds_skip_left>%d</seconds_skip_left>\n"	\
			"  <frame_width>%d</frame_width>\n" \
			"  <frame_height>%d</frame_height>\n" \
			"  <format>\"%s\"</format>\n" \
			"  <exif>\"%s\"</exif>\n" \
			"  <prefix>\"%s\"</prefix>\n" \
			"  <max_duration>%d</max_duration>\n" \
			"  <max_length>%d</max_length>\n" \
			"  <max_frames>%d</max_frames>\n" \
			"  <timescale>%f</timescale>\n"	\
			"  <frames_per_chunk>%d</frames_per_chunk>\n" \
			"  <last_error_code>%d</last_error_code>\n" \
			"  <debug_output>\"%s\"</debug_output>\n" \
			"  <debug_level>%d</debug_level>\n" \
			"  <use_global_rp>\"%s\"</use_global_rp>\n" \
			"  <kml_enable>\"%s\"</kml_enable>\n" \
			"  <kml_used>\"%s\"</kml_used>\n" \
			"  <kml_path>\"%s\"</kml_path>\n" \
			"  <kml_horHalfFov>\"%f\"</kml_horHalfFov>\n" \
			"  <kml_vertHalfFov>\"%f\"</kml_vertHalfFov>\n"	\
			"  <kml_near>\"%f\"</kml_near>\n" \
			"  <kml_height_mode>\"%s\"</kml_height_mode>\n"	\
			"  <kml_height>\"%f\"</kml_height>\n" \
			"  <kml_period>%d</kml_period>\n" \
			"  <kml_last_ts>%d.%06d</kml_last_ts>\n" \
			"  <greedy>\"%s\"</greedy>\n" \
1053 1054
			"  <ignore_fps>\"%s\"</ignore_fps>\n" \
			"  <raw_device_path>\"%s\"</raw_device_path>\n" \
1055 1056
			"  <raw_device_overruns>%d</raw_device_overruns>\n" \
			"  <raw_device_pos_write>0x%llx</raw_dev_pos_write>\n" \
1057
			"  <raw_device_pos_read>0x%llx (%d%% done)</raw_device_pos_read>\n",
1058 1059
			_state,  state->path, state->frameno, state->start_after_timestamp, _dur, _udur, _len, \
			_frames_skip, _sec_skip, \
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1060 1061
			state->width, state->height, _output_format, _using_exif, \
			state->path_prefix, state->segment_duration, state->segment_length, state->max_frames, state->timescale, \
1062
			state->frames_per_chunk,  state->last_error_code, \
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1063 1064 1065
			state->debug_name, debug_level, _using_global_pointer, \
			_kml_enable, _kml_used, state->kml_path, state->kml_horHalfFov, state->kml_vertHalfFov, state->kml_near, \
			_kml_height_mode, state->kml_height, state->kml_period, state->kml_last_ts, state->kml_last_uts, \
1066
			state->greedy ? "yes" : "no", state->ignore_fps ? "yes" : "no", state->rawdev.rawdev_path,
1067
			state->rawdev.overrun, state->rawdev.curr_pos_w, state->rawdev.curr_pos_r, _percent_done);
1068 1069 1070 1071 1072

		FOR_EACH_PORT(int, chn) {
			char *_active = is_chn_active(state, chn) ? "yes" : "no";
			fprintf(f,
			"\t<sensor_port_%d>\n" \
1073
			"\t\t<channel_active>\"%s\"</channel_active>\n" \
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
			"\t\t<compressor_state>\"%s\"</compressor_state>\n" \
			"\t\t<frame_size>%d</frame_size>\n" \
			"\t\t<frames_skip>%d</frames_skip>\n" \
			"\t\t<seconds_skip>%d</seconds_skip>\n" \
			"\t\t<buffer_overruns>%d</buffer_overruns>\n" \
			"\t\t<buffer_minimal>%d</buffer_minimal>\n" \
			"\t\t<frame_period>%d</frame_period>\n" \
			"\t\t<buffer_free>%d</buffer_free>\n" \
			"\t\t<buffer_used>%d</buffer_used>\n" \
			"\t\t<circbuf_rp>%d</circbuf_rp>\n" \
			"\t</sensor_port_%d>\n",
				chn,
				_active,
				_compressor_state[chn],
				_b_size[chn],
				_frames_remain[chn],
				_sec_remain[chn],
				state->buf_overruns[chn],
				state->buf_min[chn],
				state->frame_period[chn],
				_b_free[chn],
				_b_used[chn],
				state->cirbuf_rp[chn],
				chn
				);
1099
		}
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
		fprintf(f, "</camogm_state>\n");
	} else {
		fprintf(f, "state              \t%s\n",        _state);
		fprintf(f, "file               \t%s\n",        state->path);
		fprintf(f, "frame              \t%d\n",        state->frameno);
		fprintf(f, "start_after_timestamp \t%f\n",     state->start_after_timestamp);
		fprintf(f, "file duration      \t%d.%06d sec\n", _dur, _udur);
		fprintf(f, "file length        \t%d B\n",      _len);
		fprintf(f, "width              \t%d (0x%x)\n", state->width, state->width);
		fprintf(f, "height             \t%d (0x%x)\n", state->height, state->height);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1110
		fprintf(f, "\n");
1111 1112
		fprintf(f, "output format      \t%s\n",        _output_format);
		fprintf(f, "using exif         \t%s\n",        _using_exif);
1113 1114 1115
		fprintf(f, "path prefix        \t%s\n",        state->path_prefix);
		fprintf(f, "raw device path    \t%s\n",        state->rawdev.rawdev_path);
		fprintf(f, "raw device overruns\t%d\n",        state->rawdev.overrun);
1116 1117
		fprintf(f, "raw write position \t0x%llx\n",    state->rawdev.curr_pos_w);
		fprintf(f, "raw read position  \t0x%llx\n",    state->rawdev.curr_pos_r);
1118
		fprintf(f, "   percent done    \t%d%%\n",      _percent_done);
1119 1120
		fprintf(f, "max file duration  \t%d sec\n",    state->segment_duration);
		fprintf(f, "max file length    \t%d B\n",      state->segment_length);
1121 1122 1123 1124 1125
		fprintf(f, "max frames         \t%d\n",        state->max_frames);
		fprintf(f, "timescale          \t%f\n",        state->timescale);
		fprintf(f, "frames per chunk   \t%d\n",        state->frames_per_chunk);
		fprintf(f, "greedy             \t%s\n",        state->greedy ? "yes" : "no");
		fprintf(f, "ignore fps         \t%s\n",        state->ignore_fps ? "yes" : "no");
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1126
		fprintf(f, "\n");
1127
		fprintf(f, "last error code    \t%d\n",        state->last_error_code);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1128
		fprintf(f, "\n");
1129 1130 1131
		fprintf(f, "debug output to    \t%s\n",        state->debug_name);
		fprintf(f, "debug level        \t%d\n",        debug_level);
		fprintf(f, "use global pointer \t%s\n",        _using_global_pointer);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1132
		fprintf(f, "\n\n");
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
		fprintf(f, "kml_enable         \t%s\n",        _kml_enable);
		fprintf(f, "kml_used           \t%s\n",        _kml_used);
		fprintf(f, "kml_path           \t%s\n",        state->kml_path);
		fprintf(f, "kml_horHalfFov     \t%f degrees\n", state->kml_horHalfFov);
		fprintf(f, "kml_vertHalfFov    \t%f degrees\n", state->kml_vertHalfFov);
		fprintf(f, "kml_near           \t%f m\n",      state->kml_near);
		fprintf(f, "kml height mode    \t%s\n",        _kml_height_mode);
		fprintf(f, "kml_height (extra) \t%f m\n",      state->kml_height);
		fprintf(f, "kml_period         \t%d\n",        state->kml_period);
		fprintf(f, "kml_last_ts        \t%d.%06d\n",   state->kml_last_ts, state->kml_last_uts);
		fprintf(f, "\n");
		FOR_EACH_PORT(int, chn) {
			char *_active = is_chn_active(state, chn) ? "yes" : "no";
			fprintf(f, "===== Sensor port %d status =====\n", chn);
			fprintf(f, "enabled            \t%s\n",    _active);
			fprintf(f, "compressor state   \t%s\n",    _compressor_state[chn]);
			fprintf(f, "frame size         \t%d\n",    _b_size[chn]);
			if (_frames_skip > 0)
				fprintf(f, "frames to skip \t%d (left %d)\n", _frames_skip, _frames_remain[chn]);
			if (_sec_skip < 0 )
				fprintf(f, "timelapse period \t%d sec (remaining %d sec)\n", _sec_skip, _sec_remain[chn]);
			fprintf(f, "buffer overruns    \t%d\n",    state->buf_overruns[chn]);
			fprintf(f, "buffer minimal     \t%d\n",    state->buf_min[chn]);
			fprintf(f, "frame period       \t%d (0x%x)\n", state->frame_period[chn], state->frame_period[chn]);
			fprintf(f, "buffer free        \t%d\n",    _b_free[chn]);
			fprintf(f, "buffer used        \t%d\n",    _b_used[chn]);
			fprintf(f, "circbuf_rp         \t%d (0x%x)\n", state->cirbuf_rp[chn], state->cirbuf_rp[chn]);
			fprintf(f, "\n");
		}
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1162 1163
	}
	if ((f != stdout) && (f != stderr)) fclose(f);
1164 1165
	FOR_EACH_PORT(int, chn) {if (state->buf_overruns[chn] >= 0) state->buf_overruns[chn] = 0;}  // resets overruns after reading status , so "overruns" means since last reading status
	state->last_error_code = 0;                             // Reset error
1166
	FOR_EACH_PORT(int, chn) {state->buf_min[chn] = _b_free[chn];}
1167 1168
}

1169
/**
1170
 * @brief Read a single command from pipe
1171 1172 1173
 * @param[in]   npipe   pointer to command pipe
 * @return Pointer to null terminated string if a command is available or NULL otherwise
 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1174 1175 1176 1177
char * getLineFromPipe(FILE* npipe)
{
	int fl;
	char * nlp;
1178 1179 1180
	static char cmdbuf[1024];
	static int cmdbufp = 0; // current input pointer in the command buffer (read from pipe)
	static int cmdstrt = 0; // start of the next partial command
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1181

1182
//remove used string if any
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1183
	if (cmdstrt > 0) {
1184
//moving overlapping strings
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1185 1186 1187 1188
		memmove(cmdbuf, &cmdbuf[cmdstrt], sizeof(cmdbuf) - cmdstrt);
		cmdbufp -= cmdstrt;
		cmdstrt = 0;
	}
1189 1190
// is there any complete string in a buffer?
	if (!cmdbufp) cmdbuf[cmdbufp] = 0;  //null-terminate first access (probably not needed for the static buffer
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1191
	nlp = strpbrk(cmdbuf, ";\n");
1192
	if (!nlp) { //no complete string, try to read more
Mikhail Karpenko's avatar
Mikhail Karpenko committed
1193 1194
		fl = fread(&cmdbuf[cmdbufp], 1, sizeof(cmdbuf) - cmdbufp - 1, npipe);
		cmdbuf[cmdbufp + fl] = 0;
1195 1196 1197
// is there any complete string in a buffer after reading?
		nlp = strpbrk(&cmdbuf[cmdbufp