Commit e9e14b70 authored by Mikhail Karpenko's avatar Mikhail Karpenko

Merge branch 'index_ops'

 Conflicts:
	camogm.c
parents 30c6257b 928c20e9
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="0.531211255">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="0.531211255" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration buildProperties="" description="" id="0.531211255" name="Default" parent="org.eclipse.cdt.build.core.prefbase.cfg">
<folderInfo id="0.531211255." name="/" resourcePath="">
<toolChain id="org.eclipse.cdt.build.core.prefbase.toolchain.471666557" name="No ToolChain" resourceTypeBasedDiscovery="false" superClass="org.eclipse.cdt.build.core.prefbase.toolchain">
<targetPlatform id="org.eclipse.cdt.build.core.prefbase.toolchain.471666557.433608340" name=""/>
<builder id="org.eclipse.cdt.build.core.settings.default.builder.1922841623" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="org.eclipse.cdt.build.core.settings.default.builder"/>
<tool id="org.eclipse.cdt.build.core.settings.holder.libs.702004608" name="holder for library settings" superClass="org.eclipse.cdt.build.core.settings.holder.libs"/>
<tool id="org.eclipse.cdt.build.core.settings.holder.1963286915" name="Assembly" superClass="org.eclipse.cdt.build.core.settings.holder">
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.574933238" languageId="org.eclipse.cdt.core.assembly" languageName="Assembly" sourceContentType="org.eclipse.cdt.core.asmSource" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/>
</tool>
<tool id="org.eclipse.cdt.build.core.settings.holder.1479459009" name="GNU C++" superClass="org.eclipse.cdt.build.core.settings.holder">
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.854106261" languageId="org.eclipse.cdt.core.g++" languageName="GNU C++" sourceContentType="org.eclipse.cdt.core.cxxSource,org.eclipse.cdt.core.cxxHeader" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/>
</tool>
<tool id="org.eclipse.cdt.build.core.settings.holder.910595191" name="GNU C" superClass="org.eclipse.cdt.build.core.settings.holder">
<option id="org.eclipse.cdt.build.core.settings.holder.incpaths.1738165492" name="Include Paths" superClass="org.eclipse.cdt.build.core.settings.holder.incpaths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/linux-elphel/linux/source/include/elphel}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/linux-elphel/sysroots/elphel393/usr/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/linux-elphel/sysroots/elphel393/usr/include/ogg}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/linux-elphel/sysroots/elphel393-tcbootstrap/usr/include/linux}&quot;"/>
</option>
<inputType id="org.eclipse.cdt.build.core.settings.holder.inType.1943978810" languageId="org.eclipse.cdt.core.gcc" languageName="GNU C" sourceContentType="org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader" superClass="org.eclipse.cdt.build.core.settings.holder.inType"/>
</tool>
</toolChain>
</folderInfo>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="elphel-apps-camogm.null.1205722733" name="elphel-apps-camogm"/>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="0.1398780877">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
<scannerConfigBuildInfo instanceId="0.531211255">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope"/>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings">
<doc-comment-owner id="org.eclipse.cdt.ui.doxygen">
<path value=""/>
</doc-comment-owner>
</storageModule>
</cproject>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>camogm</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder (1).launch</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/bitbake apps-camogm -c fetch -f.launch</value>
</dictionary>
<dictionary>
<key>incclean</key>
<value>true</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/bitbake apps-camogm -c compile -f.launch</value>
</dictionary>
<dictionary>
<key>incclean</key>
<value>true</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>
This source diff could not be displayed because it is too large. You can view the blob instead.
AXIS_USABLE_LIBS = UCLIBC GLIBC
AXIS_AUTO_DEPEND = yes
#include $(AXIS_TOP_DIR)/tools/build/Rules.axis
INSTDIR = $(prefix)/usr/local/sbin/
OTHERDIR = $(prefix)/usr/html/
PHPDIR = $(prefix)/usr/html/
INSTMODE = 0755
INSTOTHER = 0644
INSTPHP = 0644
INSTOWNER = root
INSTGROUP = root
INCDIR = $(prefix)/include
PROGS = camogm
PHPFILES = camogmstate.php
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c
OBJS = camogm.o camogm_ogm.o camogm_jpeg.o camogm_mov.o camogm_kml.o
SRCS = camogm.c camogm_ogm.c camogm_jpeg.c camogm_mov.c camogm_kml.c camogm_read.c index_list.c
OBJS = camogm.o camogm_ogm.o camogm_jpeg.o camogm_mov.o camogm_kml.o camogm_read.o index_list.o
CFLAGS += -Wall -I$(ELPHEL_KERNEL_DIR)/include/elphel
#CFLAGS += -Wall -I$(INCDIR) -I$(ELPHEL_KERNEL_DIR)/include/elphel
LDLIBS += -logg -pthread -lm
all: $(PROGS)
$(PROGS): $(OBJS)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -logg -o $@
install: $(PROGS)
$(INSTALL) -d $(INSTDIR)
$(INSTALL) -m $(INSTMODE) -o $(INSTOWNER) -g $(INSTGROUP) $(PROGS) $(INSTDIR)
#already installed with ccam.cgi
$(INSTALL) -m $(INSTMODE) -o $(INSTOWNER) -g $(INSTGROUP) qt_source $(prefix)/etc
$(INSTALL) -m $(INSTPHP) -o $(INSTOWNER) -g $(INSTGROUP) $(PHPFILES) $(PHPDIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
clean:
rm -rf $(PROGS) *.o *~
configsubs:
/*!***************************************************************************
*! FILE NAME : camogm.c
*! DESCRIPTION: Program to write captured video (and audio) to camera file system
*! using Ogg container.
*! Original implementation will copy package data to a buffer to use library calls?
*! Copyright (C) 2007 Elphel, Inc.
*! -----------------------------------------------------------------------------**
*! 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/>.
*! -----------------------------------------------------------------------------**
*!
*! $Log: camogm.c,v $
*! Revision 1.13 2011/04/22 22:38:30 elphel
*! added "greedy" and "ignore_fps" options
*!
*! Revision 1.12 2010/08/16 17:10:59 elphel
*! typo
*!
*! Revision 1.11 2010/08/08 21:14:04 elphel
*! 8.0.8.38
*!
*! Revision 1.10 2010/08/07 23:38:01 elphel
*! fixed skipping (sometimes) frames at start recording
*!
*! Revision 1.9 2010/08/03 06:19:57 elphel
*! more debug for debug-level>=3
*!
*! Revision 1.8 2010/08/02 01:26:42 elphel
*! error tracking
*!
*! Revision 1.7 2010/08/01 19:30:24 elphel
*! new readonly parameter FRAME_SIZE and it support in the applications
*!
*! Revision 1.6 2010/07/18 16:59:09 elphel
*! 8.0.8.31 - added parameters to camogm (one is to start at certain absolute time, helps to synchronize multiple cameras)
*!
*! Revision 1.5 2010/07/04 19:06:02 elphel
*! moved acknowledge earlier
*!
*! Revision 1.4 2010/06/22 18:27:26 elphel
*! bug fix
*!
*! Revision 1.3 2010/06/22 16:53:30 elphel
*! camogm acknowledges received command by copying G_THIS_FRAME value to G_DAEMON_ERR+3 (default number for camogm)
*!
*! Revision 1.2 2009/02/25 17:50:02 spectr_rain
*! removed deprecated dependency
*!
*! Revision 1.1.1.1 2008/11/27 20:04:01 elphel
*!
*!
*! Revision 1.6 2008/11/21 01:52:53 elphel
*! updated for 8.0
*!
*! Revision 1.5 2008/11/20 23:21:32 elphel
*! Put FIXME notes and removed parameters that are not used anymore
*!
*! Revision 1.4 2008/10/29 04:18:28 elphel
*! v.8.0.alpha10 made a separate structure for global parameters (not related to particular frames in a frame queue)
*!
*! Revision 1.3 2008/10/13 16:55:53 elphel
*! removed (some) obsolete P_* parameters, renamed CIRCLSEEK to LSEEK_CIRC constants (same as other similar)
*!
*! Revision 1.2 2008/09/07 19:48:08 elphel
*! snapshot
*!
*! Revision 1.9 2008/04/13 21:05:19 elphel
*! Fixing KML generation
*!
*! Revision 1.8 2008/04/11 23:09:33 elphel
*! modified to handle kml generation
*!
*! Revision 1.7 2008/04/07 09:13:34 elphel
*! Changes related to new Exif generation/processing
*!
*! Revision 1.6 2008/01/14 22:59:00 elphel
*! 7.1.7.4 - added timelapse mode to camogm
*!
*! Revision 1.5 2007/12/03 08:28:45 elphel
*! Multiple changes, mostly cleanup
*!
*! Revision 1.4 2007/11/29 00:38:57 elphel
*! fixed timescale bug
*!
*! Revision 1.3 2007/11/19 05:07:19 elphel
*! fixed 2 typos
*!
*! Revision 1.2 2007/11/19 03:23:21 elphel
*! 7.1.5.5 Added support for *.mov files in camogm.
*!
*! Revision 1.1 2007/11/16 08:49:56 elphel
*! Initial release of camogm - program to record video/image to the camera hard drive (or other storage)
*!
/** @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/>.
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/fs.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <linux/limits.h>
//#include <ctype.h>
//#include <getopt.h>
#include <time.h>
#include <string.h>
#include <getopt.h>
#include <netinet/in.h> /*little <-> big endian ?*/
#include <sys/mman.h> /* mmap */
#include <sys/ioctl.h>
#include <c313a.h>
#include <exifa.h>
#include <asm/byteorder.h>
#include <ogg/ogg.h> // has to be before ogmstreams.h
#include "ogmstreams.h" // move it to <>?
//#include "camogm_exif.h"
#include "camogm_ogm.h"
#include "camogm_jpeg.h"
#include "camogm_mov.h"
#include "camogm_kml.h"
#include "camogm.h"
/* debug code follows */
// this will be defined in c313a.h
#define SENSOR_PORTS 4
#include "camogm_read.h"
#undef GLOBALPARS
#define GLOBALPARS(p, x) (globalPars[(p)][(x)-FRAMEPAR_GLOBALS])
/* end of debug code */
#define COMMAND_LOOP_DELAY 500000 //0.5sec
/** @brief Default debug level */
#define DEFAULT_DEBUG_LVL 6
/** @brief JPEG trailer syze in bytes */
#define TRAILER_SIZE 0x02
#define MAP_OPTIONS MAP_FILE | MAP_PRIVATE
/** @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 */
#define FOR_EACH_PORT(indtype, indvar) for (indtype indvar = 0; indvar < SENSOR_PORTS; indvar++)
char trailer[TRAILER_SIZE] = { 0xff, 0xd9 };
......@@ -165,40 +75,39 @@ const char *ctlFileNames[] = { "/dev/frameparsall0", "/dev/frameparsall1",
const char *circbufFileNames[] = {"/dev/circbuf0", "/dev/circbuf1",
"/dev/circbuf2", "/dev/circbuf3"
};
unsigned long * ccam_dma_buf[SENSOR_PORTS]; /* mmapped arrays */
int lastDaemonBit[SENSOR_PORTS] = {DAEMON_BIT_CAMOGM};
struct framepars_all_t *frameParsAll[SENSOR_PORTS];
struct framepars_t *framePars[SENSOR_PORTS];
unsigned long *globalPars[SENSOR_PORTS]; /// parameters that are not frame-related, their changes do not initiate any actions
#define DEFAULT_DURATION 600 /*!default segment duration (seconds) */
#define DEFAULT_LENGTH 1073741824 /*!default segment length (B) */
#define DEFAULT_GREEDY 0 /*!behavior for the files: 0 clean buffer, 1 - save as much as possible */
#define DEFAULT_IGNORE_FPS 0 /*!0 restartf file if fps changed, 1 - ignore variable fps (and skip less frames) */
//!Next 2 for Quicktime (mostly)
#define DEFAULT_FRAMES 16384 /* Maximal number of frames in file segment (each need 4* (1 + 1/frames_per_chunk) bytes for the frame index */
#define DEFAULT_FRAMES_PER_CHUNK 10 /*second sparse index - for Quicktime fast forward */
/** @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 */
camogm_state sstate;
/** @brief Memory mapped circular buffer arrays */
unsigned long * ccam_dma_buf[SENSOR_PORTS];
#define DEFAULT_LENGTH 100000000 /*!default segment length (B) */
#define DEFAULT_EXIF 1 /* use Exif */
pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
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
camogm_state sstate[SENSOR_PORTS];
//camogm_state * state;
/**
* @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
*/
typedef enum {
RAW_PATH,
FILE_PATH
} path_type;
int debug_level;
FILE* debug_file;
void camogm_init(camogm_state *state, unsigned int port, char *pipe_name);
void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num);
int camogm_start(camogm_state *state);
int camogm_stop(camogm_state *state);
int camogm_reset(camogm_state *state); //! reset circbuf read pointer
void camogm_reset(camogm_state *state);
int camogm_debug(camogm_state *state, const char *fname);
int camogm_debug_level(int d);
void camogm_set_segment_duration(camogm_state *state, int sd);
......@@ -207,10 +116,10 @@ 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);
void camogm_set_prefix(camogm_state *state, const char * p);
void camogm_set_prefix(camogm_state *state, const char * p, path_type type);
void camogm_set_exif(camogm_state *state, int d);
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
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
void camogm_set_format(camogm_state *state, int d);
void camogm_kml_set_enable(camogm_state *state, int d);
......@@ -230,8 +139,15 @@ 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);
uint64_t get_disk_size(const char *name);
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);
inline int is_chn_active(camogm_state *s, unsigned int port);
void clean_up(camogm_state *state);
//!======================================================================================================
void put_uint16(void *buf, u_int16_t val)
{
unsigned char *tmp;
......@@ -271,61 +187,63 @@ void put_uint64(void *buf, u_int64_t val)
}
/**
* @brief Initialize state for a particular sensor port.
* @param[in] state pointer to #camogm_state structure for a particular sensor channel
* @param[in] port sensor port number
* @brief Initialize the state of the program
* @param[in] state pointer to #camogm_state structure containing program state
* @param[in] pipe_name pointer to command pipe name string
* @param[in] port_num communication socket port number
* @return none
*/
void camogm_init(camogm_state *state, unsigned int port, char *pipe_name)
void camogm_init(camogm_state *state, char *pipe_name, uint16_t port_num)
{
const char sserial[] = "elp0";
int * ipser = (int*)sserial;
state->running = 0; // mo
state->starting = 0; // mo
state->vf = NULL;
memset(state, 0, sizeof(camogm_state));
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);
camogm_set_start_after_timestamp(state, 0.0); // start any time
camogm_set_prefix(state, "\0");
camogm_set_save_gp(state, 0);
camogm_reset(state); // sets state->buf_overruns =- 1
state->serialno = ipser[0];
state->last = 0;
debug_file = stderr;
camogm_debug_level(DEFAULT_DEBUG_LVL);
strcpy(state->debug_name, "stderr");
camogm_set_timescale(state, 1.0);
camogm_set_frames_skip(state, 0); // don't skip
camogm_set_format(state, CAMOGM_FORMAT_MOV);
state->exifSize = 0;
state->exif = DEFAULT_EXIF;
state->frame_lengths = NULL;
state->frameno = 0;
state->formats = 0;
state->last_error_code = 0;
// reading thread has not been started yet, mutex lock is not necessary
state->prog_state = STATE_STOPPED;
state->rawdev.thread_state = STATE_STOPPED;
// kml stuff
camogm_kml_set_enable(state, 0);
state->kml_file = NULL;
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);
camogm_kml_set_period(state, 2); // 2 sec
camogm_kml_set_near(state, 40.0); // 40 m (distance to PhotoOverlay)
state->kml_path[0] = '\0';
state->port_num = port;
state->pipe_name = pipe_name;
state->rawdev.start_pos = RAWDEV_START_OFFSET;
state->rawdev.end_pos = state->rawdev.start_pos;
state->rawdev.curr_pos_w = state->rawdev.start_pos;
state->rawdev.curr_pos_r = state->rawdev.start_pos;
state->active_chn = ALL_CHN_ACTIVE;
state->rawdev.mmap_default_size = MMAP_CHUNK_SIZE;
state->sock_port = port_num;
}
/**
* @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
*/
int camogm_debug(camogm_state *state, const char * fname)
{
int none = 1;
......@@ -348,25 +266,41 @@ int camogm_debug(camogm_state *state, const char * fname)
return 0;
}
/** @brief Set current debug level */
int camogm_debug_level(int d)
{
debug_level = d;
return 0;
}
/**
* @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
*/
int camogm_start(camogm_state *state)
{
int timestamp_start;
int rslt;
int next_metadata_start, next_jpeg_len, fp;
int port = state->port_num;
if (state->active_chn == ALL_CHN_INACTIVE) {
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;
}
D1(fprintf(debug_file, "Starting recording\n"));
double dtime_stamp;
state->frameno = 0;
state->timescale = state->set_timescale; //! current timescale, default 1
///debug
int * ifp = (int*)&(state->frame_params);
int * ifp_this = (int*)&(state->this_frame_params);
state->timescale = state->set_timescale; // current timescale, default 1
// debug
int * ifp = (int*)&(state->frame_params[port]);
int * ifp_this = (int*)&(state->this_frame_params[port]);
if (state->kml_enable) camogm_init_kml(); // do nothing
if (state->format != state->set_format) {
......@@ -374,143 +308,144 @@ int camogm_start(camogm_state *state)
switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(); break;
}
state->formats |= 1 << (state->format);
//! exit on unknown formats?
// exit on unknown formats?
}
state->max_frames = state->set_max_frames;
state->frames_per_chunk = state->set_frames_per_chunk;
state->starting = 1; //!may be already set
//! Check/set circbuf read pointer
D3(fprintf(debug_file, "1: state->cirbuf_rp=0x%x\n", state->cirbuf_rp));
if ((state->cirbuf_rp < 0) || (lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET) < 0) || (lseek(state->fd_circ, LSEEK_CIRC_VALID, SEEK_END) < 0 )) {
D3(fprintf(debug_file, "2: state->cirbuf_rp=0x%x\n", state->cirbuf_rp));
// state->cirbuf_rp=lseek(state->fd_circ,LSEEK_CIRC_LAST,SEEK_END);
/* In "greedy" mode try to save as many frames from the circbuf as possible */
state->cirbuf_rp = lseek(state->fd_circ, state->greedy ? LSEEK_CIRC_SCND : LSEEK_CIRC_LAST, SEEK_END);
pthread_mutex_lock(&state->mutex);
state->prog_state = STATE_STARTING;
pthread_mutex_unlock(&state->mutex);
FOR_EACH_PORT(int, 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, LSEEK_CIRC_PREV, SEEK_END))) >= 0) state->cirbuf_rp = fp; //!try to have 2 frames available for fps
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++;
//! file pointer here should match state->rp; so no need to do lseek(state->fd_circ,state->cirbuf_rp,SEEK_SET);
state->buf_min = getGPValue(state->port_num, G_FREECIRCBUF);
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 > getGPValue(state->port_num, G_FREECIRCBUF)) state->buf_min = getGPValue(state->port_num, G_FREECIRCBUF);
if (state->buf_min[chn] > getGPValue(chn, G_FREECIRCBUF)) state->buf_min[chn] = getGPValue(chn, G_FREECIRCBUF);
}
D3(fprintf(debug_file, "3: state->cirbuf_rp=0x%x\n", state->cirbuf_rp));
D3(fprintf(debug_file, "4:lseek(state->fd_circ,LSEEK_CIRC_READY,SEEK_END)=%d\n", (int)lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END)));
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)));
//! is this frame ready?
if (lseek(state->fd_circ, 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));
state->metadata_start = (state->cirbuf_rp) - 32;
if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size;
// 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];
///==================================
// ==================================
memcpy(&(state->frame_params), (unsigned long* )&ccam_dma_buf[state->port_num][state->metadata_start >> 2], 32);
state->jpeg_len = state->frame_params.frame_length; //! frame_params.frame_length are now the length of bitstream
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
if (state->frame_params.signffff != 0xffff) {
D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->frame_params.signffff));
state->cirbuf_rp = -1;
D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp));
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));
state->cirbuf_rp[chn] = -1;
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;
}
//! find location of the timestamp and copy it to the frame_params structure
///==================================
timestamp_start = (state->cirbuf_rp) + ((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) timestamp_start -= state->circ_buff_size;
memcpy(&(state->frame_params.timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][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.timestamp_usec + state->frame_params.timestamp_sec;
// 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 = -1;
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));
//! see if next frame is available
if ((lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END) < 0 ) ||
//! is that next frame ready?
(((fp = lseek(state->fd_circ, 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, LSEEK_CIRC_NEXT, SEEK_END), (int)lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END)));
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, state->cirbuf_rp, SEEK_SET); //!just in case - restore pointer
return -CAMOGM_FRAME_NOT_READY; //! frame pointer valid, but no frames yet
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;
memcpy(&(state->this_frame_params), (unsigned long* )&ccam_dma_buf[state->port_num][next_metadata_start >> 2], 32);
next_jpeg_len = state->this_frame_params.frame_length; //! frame_params.frame_length are now the length of bitstream
if (state->this_frame_params.signffff != 0xffff) { //! should not happen ever
D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->this_frame_params.signffff));
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]));
// int * ifp = (int *) &(state->this_frame_params) ;
// int * ifp_this = (int *) &(state->this_frame_params) ;
state->cirbuf_rp = -1;
state->cirbuf_rp[chn] = -1;
return -CAMOGM_FRAME_BROKEN;
}
D3(fprintf(debug_file, "7: state->cirbuf_rp=0x%x\n", state->cirbuf_rp));
//! 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) timestamp_start -= state->circ_buff_size;
memcpy(&(state->this_frame_params.timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][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.width != state->this_frame_params.width) ||
(state->frame_params.height != state->this_frame_params.height))) {
//! Advance frame pointer to the next (caller should try again)
state->cirbuf_rp = fp;
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));
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 = (state->this_frame_params.timestamp_usec - state->frame_params.timestamp_usec) +
1000000 * (state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec);
// 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:
// correct for timelapse modes:
state->frames_skip = state->set_frames_skip;
if (state->frames_skip > 0) {
state->frames_skip_left = 0;
state->frame_period *= (state->frames_skip + 1);
// state->frames_skip_left= state->set_frames_skip;
state->frames_skip_left[chn] = 0;
state->frame_period[chn] *= (state->frames_skip + 1);
} else if (state->frames_skip < 0) {
state->frame_period = -(state->frames_skip); //! actual frame period will fluctuate to the nearest frame acquired (free running)
state->frames_skip_left = state->frame_params.timestamp_sec;
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));
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) * ((double)10) / ((double)state->timescale));
state->width = state->frame_params.width;
state->height = state->frame_params.height;
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, state->cirbuf_rp + 1, SEEK_END); //!+1 to avoid condition when jpeg_start==0. overloaded lseek will ignore 5 LSBs when SEEK_END
state->head_size = lseek(state->fd_head, 0, SEEK_END); /// In 8.0 the header size might change for some jp4 modes
if (state->head_size > JPEG_HEADER_MAXSIZE) {
D0(fprintf(debug_file, "%s:%d: Too big JPEG header (%d > %d)", __FILE__, __LINE__, state->head_size, JPEG_HEADER_MAXSIZE ));
// 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, 0, 0);
read(state->fd_head, state->jpegHeader, state->head_size);
//! Restore read pointer to the original (now there may be no frame ready there yet)
lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET);
// 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);
}
//!here we are ready to initialize Ogm (or other) file
// here we are ready to initialize Ogm (or other) file
switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_start_ogm(state); break;
......@@ -524,161 +459,163 @@ int camogm_start(camogm_state *state)
}
if (state->kml_enable) rslt = camogm_start_kml(state); // will turn on state->kml_used if it can
if (rslt) return rslt;
state->running = 1;
state->starting = 0;
pthread_mutex_lock(&state->mutex);
state->prog_state = STATE_RUNNING;
pthread_mutex_unlock(&state->mutex);
D1(fprintf(debug_file, "Started OK\n"));
return 0;
}
/**
* @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
*/
int sendImageFrame(camogm_state *state)
{
int rslt;
unsigned char frame_packet_type = PACKET_IS_SYNCPOINT;
int timestamp_start;
///debugging:
// int * ifp = (int *) &(state->frame_params) ;
int * ifp_this = (int*)&(state->this_frame_params);
int * ifp_this = (int*)&(state->this_frame_params[state->port_num]);
int fp;
int port = state->port_num;
//! This is probably needed only for Quicktime (not to exceed already allocated frame index)
// This is probably needed only for Quicktime (not to exceed already allocated frame index)
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;
}
//! Format changed?
// Format changed?
// D3(fprintf (debug_file,"sendImageFrame: format=%d, set_format=%d\n", state->format, state->set_format));
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;
}
//! check if file size is exceeded (assuming fopen),-CAMOGM_FRAME_CHANGED will trigger a new segment
// check if file size is exceeded (assuming fopen),-CAMOGM_FRAME_CHANGED will trigger a new segment
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;
}
//!same for open
//same for open
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;
}
//! check the frame pointer is valid
if ((fp = lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET)) < 0) {
// check the frame pointer is valid
if ((fp = lseek(state->fd_circ[port], state->cirbuf_rp[port], SEEK_SET)) < 0) {
D3(fprintf(debug_file, "sendImageFrame:5: invalid frame\n"));
return -CAMOGM_FRAME_INVALID; //!it will probably be that allready
return -CAMOGM_FRAME_INVALID; //it will probably be that allready
}
//! is the frame ready?
if (lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END) < 0) {
// is the frame ready?
if (lseek(state->fd_circ[port], LSEEK_CIRC_READY, SEEK_END) < 0) {
D3(fprintf(debug_file, "?6,fp=0x%x ", fp)); //frame not ready, frame pointer seems valid, but not ready
return -CAMOGM_FRAME_NOT_READY; //! frame pointer valid, but no frames yet
return -CAMOGM_FRAME_NOT_READY; // frame pointer valid, but no frames yet
}
//! 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 > 0 )) { //!skipping frames, not seconds.
state->cirbuf_rp = lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END);
//!optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ, LSEEK_CIRC_SETP, SEEK_END);
state->frames_skip_left--;
// 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.
state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
//optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
state->frames_skip_left[port]--;
D3(fprintf(debug_file, "?7 ")); //frame not ready
return -CAMOGM_FRAME_NOT_READY; //! the required frame is not ready
return -CAMOGM_FRAME_NOT_READY; // the required frame is not ready
}
//! Get metadata
// Get metadata
D3(fprintf(debug_file, "_1_"));
state->metadata_start = state->cirbuf_rp - 32;
if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size;
memcpy(&(state->this_frame_params), (unsigned long* )&ccam_dma_buf[state->port_num][state->metadata_start >> 2], 32);
state->jpeg_len = state->this_frame_params.frame_length; //! frame_params.frame_length are now the length of bitstream
if (state->this_frame_params.signffff != 0xffff) {
D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->this_frame_params.signffff));
D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp));
state->metadata_start = state->cirbuf_rp[port] - 32;
if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size[port];
memcpy(&(state->this_frame_params[port]), (unsigned long* )&ccam_dma_buf[state->port_num][state->metadata_start >> 2], 32);
state->jpeg_len = state->this_frame_params[port].frame_length; // frame_params.frame_length are now the length of bitstream
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));
D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp[port]));
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_"));
//! find location of the timestamp and copy it to the frame_params structure
timestamp_start = state->cirbuf_rp + ((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) timestamp_start -= state->circ_buff_size;
// 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
if (timestamp_start >= state->circ_buff_size[port]) timestamp_start -= state->circ_buff_size[port];
D3(fprintf(debug_file, "_3_"));
memcpy(&(state->this_frame_params.timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][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.width != state->this_frame_params.width) ||
(state->frame_params.height != state->this_frame_params.height))) {
memcpy(&(state->this_frame_params[port].timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][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[port].width != state->this_frame_params[port].width) ||
(state->frame_params[port].height != state->this_frame_params[port].height))) {
D3(fprintf(debug_file, "sendImageFrame:9: WOI changed\n"));
return -CAMOGM_FRAME_CHANGED; //! not yet checking for the FPS
return -CAMOGM_FRAME_CHANGED; // not yet checking for the FPS
}
//! check if file duration (in seconds) exceeded ,-CAMOGM_FRAME_CHANGED will trigger a new segment
// check if file duration (in seconds) exceeded ,-CAMOGM_FRAME_CHANGED will trigger a new segment
if ((state->segment_duration > 0) &&
((state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec) > state->segment_duration)) {
((state->this_frame_params[port].timestamp_sec - state->frame_params[port].timestamp_sec) > state->segment_duration)) {
D3(fprintf(debug_file, "sendImageFrame:10: segment duration in seconds exceeded\n"));
return -CAMOGM_FRAME_CHANGED;
}
//! check if (in timelapse mode) it is too early for the frame to be stored
if ((state->frames_skip < 0) && (state->frames_skip_left > state->this_frame_params.timestamp_sec) ) {
state->cirbuf_rp = lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END);
//!optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ, LSEEK_CIRC_SETP, SEEK_END);
// check if (in timelapse mode) it is too early for the frame to be stored
if ((state->frames_skip < 0) && (state->frames_skip_left[port] > state->this_frame_params[port].timestamp_sec) ) {
state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
//optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
D3(fprintf(debug_file, "sendImageFrame:11: timelapse: frame will be skipped\n"));
return -CAMOGM_FRAME_NOT_READY; //! the required frame is not ready
return -CAMOGM_FRAME_NOT_READY; // the required frame is not ready
}
D3(fprintf(debug_file, "_4_"));
if (state->exif) {
D3(fprintf(debug_file, "_5_"));
//! update the Exif header with the current frame metadata
// updateExif(ep, state->ed, &(state->frame_params));
state->exifSize = lseek(state->fd_exif, 1, SEEK_END); // at the beginning of page 1 - position == page length
// if (state->exifSize < 0) state->exifSize=0; // error from lseek;
// update the Exif header with the current frame metadata
state->exifSize[port] = lseek(state->fd_exif[port], 1, SEEK_END); // at the beginning of page 1 - position == page length
if (state->exifSize > 0) {
//state->this_frame_params.meta_index
lseek(state->fd_exif, state->this_frame_params.meta_index, SEEK_END); //! select meta page to use (matching frame)
rslt = read(state->fd_exif, state->ed, state->exifSize);
lseek(state->fd_exif[port], state->this_frame_params[port].meta_index, SEEK_END); // select meta page to use (matching frame)
rslt = read(state->fd_exif[port], state->ed[port], state->exifSize[port]);
if (rslt < 0) rslt = 0;
state->exifSize = rslt;
} else state->exifSize = 0;
} else state->exifSize = 0;
state->exifSize[port] = rslt;
} else state->exifSize[port] = 0;
} else state->exifSize[port] = 0;
D3(fprintf(debug_file, "_6_"));
//! prepare a packet to be sent (a lst of memory chunks)
// prepare a packet to be sent (a lst of memory chunks)
state->chunk_index = 0;
state->packetchunks[state->chunk_index ].bytes = 1;
state->packetchunks[state->chunk_index++].chunk = &frame_packet_type;
if (state->exif > 0) { //! insert Exif
if (state->exif > 0) { // insert Exif
D3(fprintf(debug_file, "_7_"));
state->packetchunks[state->chunk_index ].bytes = 2;
state->packetchunks[state->chunk_index++].chunk = state->jpegHeader;
state->packetchunks[state->chunk_index ].bytes = state->exifSize;
state->packetchunks[state->chunk_index++].chunk = state->ed;
state->packetchunks[state->chunk_index ].bytes = state->head_size - 2;
state->packetchunks[state->chunk_index++].chunk = &(state->jpegHeader[2]);
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]);
} else {
D3(fprintf(debug_file, "_8_"));
state->packetchunks[state->chunk_index ].bytes = state->head_size;
state->packetchunks[state->chunk_index++].chunk = state->jpegHeader;
state->packetchunks[state->chunk_index ].bytes = state->head_size[port];
state->packetchunks[state->chunk_index++].chunk = state->jpegHeader[port];
}
D3(fprintf(debug_file, "_9_"));
/*! JPEG image data may be split in two segments (rolled over buffer end) - process both variants */
if ((state->cirbuf_rp + state->jpeg_len) > state->circ_buff_size) { //! two segments
/*! copy from the beginning of the frame to the end of the buffer */
/* 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 */
D3(fprintf(debug_file, "_10_"));
state->packetchunks[state->chunk_index ].bytes = state->circ_buff_size - state->cirbuf_rp;
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp >> 2];
/*! copy from the beginning of the buffer to the end of the frame */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len - (state->circ_buff_size - state->cirbuf_rp);
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];
/* copy from the beginning of the buffer to the end of the frame */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len - (state->circ_buff_size[port] - state->cirbuf_rp[port]);
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][0];
} else { // single segment
D3(fprintf(debug_file, "_11_"));
/*! copy from the beginning of the frame to the end of the frame (no buffer rollovers) */
/* copy from the beginning of the frame to the end of the frame (no buffer rollovers) */
state->packetchunks[state->chunk_index ].bytes = state->jpeg_len;
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp >> 2];
state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp[port] >> 2];
}
D3(fprintf(debug_file, "_12_"));
state->packetchunks[state->chunk_index ].bytes = 2;
......@@ -699,30 +636,37 @@ int sendImageFrame(camogm_state *state)
if (rslt) return rslt;
D3(fprintf(debug_file, "_14_"));
//!advance frame pointer
// advance frame pointer
state->frameno++;
state->cirbuf_rp = lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END);
//!optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ, LSEEK_CIRC_SETP, SEEK_END);
state->cirbuf_rp[port] = lseek(state->fd_circ[port], LSEEK_CIRC_NEXT, SEEK_END);
// optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers")
if (state->save_gp) lseek(state->fd_circ[port], LSEEK_CIRC_SETP, SEEK_END);
D3(fprintf(debug_file, "_15_\n"));
if (state->frames_skip > 0) {
state->frames_skip_left = state->frames_skip;
state->frames_skip_left[port] = state->frames_skip;
} else if (state->frames_skip < 0) {
state->frames_skip_left += -(state->frames_skip);
state->frames_skip_left[port] += -(state->frames_skip);
}
return 0;
}
/**
* @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
*/
int camogm_stop(camogm_state *state)
{
int rslt = 0;
if (!state->running) {
if (!state->starting) {
if (state->prog_state != STATE_RUNNING) {
if (state->prog_state != STATE_STARTING) {
D2(fprintf(debug_file, "Recording was not running, nothing to stop\n"));
} else {
state->starting = 0;
pthread_mutex_lock(&state->mutex);
state->prog_state = STATE_STOPPED;
pthread_mutex_unlock(&state->mutex);
D1(fprintf(debug_file, "Dropping attempt to start\n"));
}
return 0;
......@@ -732,28 +676,30 @@ int camogm_stop(camogm_state *state)
switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_end_ogm(state); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_end_jpeg(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_end_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_end_mov(state); break;
/// default: return 0; // do nothing
// default: return 0; // do nothing
}
//! now close video file (if it is open)
// now close video file (if it is open)
if (state->vf) fclose(state->vf);
state->vf = NULL;
if (rslt) return rslt;
state->last = 1;
//!state->running=0 should be output after file is finished and closed
state->running = 0;
state->starting = 0;
pthread_mutex_lock(&state->mutex);
state->prog_state = STATE_STOPPED;
pthread_mutex_unlock(&state->mutex);
return 0;
}
/**
* @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
*/
void camogm_free(camogm_state *state)
{
int f;
//! free all file format handlers that were used
//add kml when needed
for (f = 0; f < 31; f++) {
for (int f = 0; f < 31; f++) {
if (state->formats & ( 1 << (state->format))) {
switch (f) {
case CAMOGM_FORMAT_NONE: break;
......@@ -766,41 +712,52 @@ void camogm_free(camogm_state *state)
state->formats = 0;
}
int camogm_reset(camogm_state *state) //! reset circbuf read pointer
/**
* @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)
{
state->cirbuf_rp = -1;
state->buf_overruns = -1; //!first will not count
return 0;
FOR_EACH_PORT(int, chn) {
state->cirbuf_rp[chn] = -1;
state->buf_overruns[chn] = -1; // first overrun will not count
}
}
///kml stuff
/** @brief kml parameter setter */
void camogm_kml_set_enable(camogm_state *state, int d)
{
state->kml_enable = d;
}
/** @brief kml parameter setter */
void camogm_kml_set_horHalfFov(camogm_state *state, double dd)
{
state->kml_horHalfFov = dd;
}
/** @brief kml parameter setter */
void camogm_kml_set_vertHalfFov(camogm_state *state, double dd)
{
state->kml_vertHalfFov = dd;
}
/** @brief kml parameter setter */
void camogm_kml_set_height_mode(camogm_state *state, int d)
{
state->kml_height_mode = d;
}
/** @brief kml parameter setter */
void camogm_kml_set_height(camogm_state *state, double dd)
{
state->kml_height = dd;
}
/** @brief kml parameter setter */
void camogm_kml_set_period(camogm_state *state, int d)
{
state->kml_period = d;
state->kml_last_ts = 0;
state->kml_last_uts = 0;
}
/** @brief kml parameter setter */
void camogm_kml_set_near(camogm_state *state, double dd) // distance to PhotoOverlay
{
state->kml_near = dd;
......@@ -824,7 +781,6 @@ void camogm_set_exif(camogm_state *state, int d)
state->exif = d;
}
void camogm_set_greedy(camogm_state *state, int d)
{
state->greedy = d ? 1 : 0;
......@@ -834,42 +790,66 @@ void camogm_set_ignore_fps(camogm_state *state, int d)
state->ignore_fps = d ? 1 : 0;
}
void camogm_set_prefix(camogm_state *state, const char * p)
/**
* @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
*/
void camogm_set_prefix(camogm_state *state, const char * p, path_type type)
{
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);
if (state->rawdev.end_pos == 0) {
state->rawdev_op = 0;
state->rawdev.end_pos = state->rawdev.start_pos;
state->rawdev.rawdev_path[0] = '\0';
D0(fprintf(debug_file, "ERROR: unable to initiate raw device operation\n"));
} else {
D0(fprintf(debug_file, "WARNING: raw device write initiated\n"));
state->rawdev_op = 1;
}
}
}
void camogm_set_timescale(camogm_state *state, double d) //! set timescale, default=1,000,000
/** @brief Set timescale @e d, default = 1,000,000 */
void camogm_set_timescale(camogm_state *state, double d)
{
state->set_timescale = d;
if ((state->running == 0) && (state->starting == 0)) {
if (state->prog_state == STATE_STOPPED) {
state->timescale = state->set_timescale;
}
}
void camogm_set_frames_skip(camogm_state *state, int d) //! set frames to skip (for time lapse)
/** @brief Set the number of frames @e d to be skipped during recording. This values is common for each sensor port */
void camogm_set_frames_skip(camogm_state *state, int d)
{
state->set_frames_skip = d;
if ((state->running == 0) && (state->starting == 0)) {
if (state->prog_state == STATE_STOPPED) {
state->frames_skip = state->set_frames_skip;
// state->frames_skip_left= state->set_fram_skip;
state->frames_skip_left = 0;
state->frames_skip_left[state->port_num] = 0;
}
}
/** @brief Set file format @e d the images will be recorded to. This function calls corresponding @e camogm_init_*
* function for the format selected.*/
void camogm_set_format(camogm_state *state, int d)
{
int rslt = 0;
state->set_format = d;
if ((state->running == 0) && (state->starting == 0)) {
if (state->prog_state == STATE_STOPPED) {
state->format = state->set_format;
switch (state->format) {
case CAMOGM_FORMAT_NONE: rslt = 0; break;
case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(); break;
case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(state); break;
case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(); break;
}
if (rslt) {
......@@ -878,45 +858,87 @@ void camogm_set_format(camogm_state *state, int d)
state->formats |= 1 << (state->format);
}
}
//! needed for Quicktime - maybe something else?
/** @brief Set max number of frames @e d */
void camogm_set_max_frames(camogm_state *state, int d)
{
state->set_max_frames = d;
if ((state->running == 0) && (state->starting == 0)) state->max_frames = d;
if (state->prog_state == STATE_STOPPED)
state->max_frames = d;
}
/** @brief Set the number of frames @e d recorded per chunk */
void camogm_set_frames_per_chunk(camogm_state *state, int d)
{
state->set_frames_per_chunk = d;
if ((state->running == 0) && (state->starting == 0)) state->frames_per_chunk = d;
if (state->prog_state == STATE_STOPPED)
state->frames_per_chunk = d;
}
/** @brief Set the time stamp @e d when recording should be started */
void camogm_set_start_after_timestamp(camogm_state *state, double d)
{
state->start_after_timestamp = d;
}
/**
* @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
* @note access to state->rawdev.curr_pos_r is not locked in reading thread
* @return None
*/
void camogm_status(camogm_state *state, char * fn, int xml)
{
int _len = 0;
int _dur, _udur;
//TODO:make it XML file
int _dur = 0, _udur = 0, _dur_raw, _udur_raw;
FILE* f;
char *_state, *_output_format, *_using_exif, *_using_global_pointer, *_compressor_state;
int _b_free, _b_used, _b_size; // , save_p;
int _frames_remain = 0;
int _sec_remain = 0;
char *_state, *_output_format, *_using_exif, *_using_global_pointer, *_compressor_state[SENSOR_PORTS];
int _b_free[SENSOR_PORTS], _b_used[SENSOR_PORTS], _b_size[SENSOR_PORTS];
int _frames_remain[SENSOR_PORTS] = {0};
int _sec_remain[SENSOR_PORTS] = {0};
int _frames_skip = 0;
int _sec_skip = 0;
char *_kml_enable, *_kml_used, *_kml_height_mode;
unsigned int _percent_done;
_kml_enable = state->kml_enable ? "yes" : "no";
_kml_used = state->kml_used ? "yes" : "no";
_kml_height_mode = state->kml_height_mode ? "GPS altitude" : "map ground level"; //! 1 - actual, 0 - ground
_b_free = getGPValue(state->port_num, G_FREECIRCBUF);
_b_used = getGPValue(state->port_num, G_CIRCBUFSIZE) - getGPValue(state->port_num, G_FREECIRCBUF);
_b_size = getGPValue(state->port_num, G_FRAME_SIZE);
_kml_height_mode = state->kml_height_mode ? "GPS altitude" : "map ground level"; // 1 - actual, 0 - ground
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);
}
if (!fn) f = stdout;
else if (strcmp(fn, "stdout") == 0) f = stdout;
......@@ -927,48 +949,44 @@ void camogm_status(camogm_state *state, char * fn, int xml)
return;
}
}
if (state->vf) _len = ftell(state->vf); //! for ogm
else if ((state->ivf) >= 0) _len = lseek(state->ivf, 0, SEEK_CUR); //!for mov
_dur = state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec;
_udur = state->this_frame_params.timestamp_usec - state->frame_params.timestamp_usec;
if (_udur < 0) {
_dur -= 1;
_udur += 1000000;
} else if (_udur >= 1000000) {
_dur += 1;
_udur -= 1000000;
if (state->vf) _len = ftell(state->vf); // for ogm
else if ((state->ivf) >= 0) _len = lseek(state->ivf, 0, SEEK_CUR); //for mov
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;
default:
_state = "stopped";
}
_state = state->running ? "running" : (state->starting ? "starting" : "stopped");
_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";
_compressor_state = (getGPValue(state->port_num, P_COMPRESSOR_RUN) == 2) ? "running" : "stopped";
if ( state->frames_skip > 0 ) {
_frames_remain = state->frames_skip_left;
_frames_skip = state->frames_skip;
} else if ( state->frames_skip < 0 ) {
_sec_remain = (state->frames_skip_left - state->this_frame_params.timestamp_sec);
_sec_skip = -(state->frames_skip);
}
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;
if (xml) {
fprintf(f, "<?xml version=\"1.0\"?>\n" \
"<camogm_state>\n" \
" <state>\"%s\"</state>\n" \
" <compressor_state>\"%s\"</compressor_state>\n" \
" <file_name>\"%s\"</file_name>\n" \
" <frame_number>%d</frame_number>\n" \
" <frame_size>%d</frame_size>\n" \
" <start_after_timestamp>%f</start_after_timestamp>\n" \
" <file_duration>%d.%06d</file_duration>\n" \
" <file_length>%d</file_length>\n" \
" <frame_period>%d</frame_period>\n" \
" <frames_skip>%d</frames_skip>\n" \
" <seconds_skip>%d</seconds_skip>\n" \
" <frames_skip_left>%d</frames_skip_left>\n" \
" <seconds_skip_left>%d</seconds_skip_left>\n" \
" <frame_width>%d</frame_width>\n" \
......@@ -982,11 +1000,6 @@ void camogm_status(camogm_state *state, char * fn, int xml)
" <timescale>%f</timescale>\n" \
" <frames_per_chunk>%d</frames_per_chunk>\n" \
" <last_error_code>%d</last_error_code>\n" \
" <buffer_overruns>%d</buffer_overruns>\n" \
" <buffer_minimal>%d</buffer_minimal>\n" \
" <buffer_free>%d</buffer_free>\n" \
" <buffer_used>%d</buffer_used>\n" \
" <circbuf_rp>%d</circbuf_rp>\n" \
" <debug_output>\"%s\"</debug_output>\n" \
" <debug_level>%d</debug_level>\n" \
" <use_global_rp>\"%s\"</use_global_rp>\n" \
......@@ -1002,112 +1015,168 @@ void camogm_status(camogm_state *state, char * fn, int xml)
" <kml_last_ts>%d.%06d</kml_last_ts>\n" \
" <greedy>\"%s\"</greedy>\n" \
" <ignore_fps>\"%s\"</ignore_fps>\n" \
"</camogm_state>\n",
_state, _compressor_state, state->path, state->frameno, _b_size, state->start_after_timestamp, _dur, _udur, _len, state->frame_period, \
_frames_skip, _sec_skip, _frames_remain, _sec_remain, \
" <raw_device_path>\"%s\"</raw_device_path>\n" \
" <raw_device_overruns>%d</raw_device_overruns>\n" \
" <raw_device_pos_write>0x%llx</raw_dev_pos_write>\n" \
" <raw_device_pos_read>0x%llx (%d%% done)</raw_device_pos_read>\n",
_state, state->path, state->frameno, state->start_after_timestamp, _dur, _udur, _len, \
_frames_skip, _sec_skip, \
state->width, state->height, _output_format, _using_exif, \
state->path_prefix, state->segment_duration, state->segment_length, state->max_frames, state->timescale, \
state->frames_per_chunk, state->last_error_code, state->buf_overruns, state->buf_min, _b_free, _b_used, state->cirbuf_rp, \
state->frames_per_chunk, state->last_error_code, \
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, \
state->greedy ? "yes" : "no", state->ignore_fps ? "yes" : "no");
state->greedy ? "yes" : "no", state->ignore_fps ? "yes" : "no", state->rawdev.rawdev_path,
state->rawdev.overrun, state->rawdev.curr_pos_w, state->rawdev.curr_pos_r, _percent_done);
FOR_EACH_PORT(int, chn) {
char *_active = is_chn_active(state, chn) ? "yes" : "no";
fprintf(f,
"\t<sensor_port_%d>\n" \
"\t\t<channel_active>\"%s\"</channel_active>\n" \
"\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
);
}
fprintf(f, "</camogm_state>\n");
} else {
fprintf(f, "state %s\n", _state);
fprintf(f, "compressor state %s\n", _compressor_state);
fprintf(f, "file %s\n", state->path);
fprintf(f, "frame %d\n", state->frameno);
fprintf(f, "frame size %d\n", _b_size);
fprintf(f, "start_after_timestamp %f\n", state->start_after_timestamp);
fprintf(f, "file duration %d.%06d sec\n", _dur, _udur);
fprintf(f, "file length %d B\n", _len);
fprintf(f, "frame period %d (0x%x)\n", state->frame_period, state->frame_period);
if ( _frames_skip > 0 ) fprintf(f, "frames to skip %d (left %d)\n", _frames_skip, _frames_remain);
if ( _sec_skip < 0 ) fprintf(f, "timelapse period %d sec (remaining %d sec)\n", _sec_skip, _sec_remain);
fprintf(f, "width %d (0x%x)\n", state->width, state->width);
fprintf(f, "height %d (0x%x)\n", state->height, state->height);
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);
fprintf(f, "\n");
fprintf(f, "output format %s\n", _output_format);
fprintf(f, "using exif %s\n", _using_exif);
fprintf(f, "path prefix: %s\n", state->path_prefix);
fprintf(f, "max file duration: %d sec\n", state->segment_duration);
fprintf(f, "max file length: %d B\n", state->segment_length);
fprintf(f, "max frames %d\n", state->max_frames);
fprintf(f, "timescale %f\n", state->timescale);
fprintf(f, "frames per chunk %d\n", state->frames_per_chunk);
fprintf(f, "greedy %s\n", state->greedy ? "yes" : "no");
fprintf(f, "ignore fps %s\n", state->ignore_fps ? "yes" : "no");
fprintf(f, "output format \t%s\n", _output_format);
fprintf(f, "using exif \t%s\n", _using_exif);
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);
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);
fprintf(f, " percent done \t%d%%\n", _percent_done);
fprintf(f, "max file duration \t%d sec\n", state->segment_duration);
fprintf(f, "max file length \t%d B\n", state->segment_length);
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");
fprintf(f, "\n");
fprintf(f, "last error code %d\n", state->last_error_code);
fprintf(f, "buffer overruns %d\n", state->buf_overruns);
fprintf(f, "buffer minimal %d\n", state->buf_min);
fprintf(f, "buffer free %d\n", _b_free);
fprintf(f, "buffer used %d\n", _b_used);
fprintf(f, "circbuf_rp %d (0x%x)\n", state->cirbuf_rp, state->cirbuf_rp);
fprintf(f, "last error code \t%d\n", state->last_error_code);
fprintf(f, "\n");
fprintf(f, "debug output to %s\n", state->debug_name);
fprintf(f, "debug level %d\n", debug_level);
fprintf(f, "use global pointer %s\n", _using_global_pointer);
fprintf(f, "\n\n");
fprintf(f, "kml_enable %s\n", _kml_enable);
fprintf(f, "kml_used %s\n", _kml_used);
fprintf(f, "kml_path %s\n", state->kml_path);
fprintf(f, "kml_horHalfFov %f degrees\n", state->kml_horHalfFov);
fprintf(f, "kml_vertHalfFov %f degrees\n", state->kml_vertHalfFov);
fprintf(f, "kml_near %f m\n", state->kml_near);
fprintf(f, "kml height mode %s\n", _kml_height_mode);
fprintf(f, "kml_height (extra) %f m\n", state->kml_height);
fprintf(f, "kml_period %d\n", state->kml_period);
fprintf(f, "kml_last_ts %d.%06d\n", state->kml_last_ts, state->kml_last_uts);
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);
fprintf(f, "\n\n");
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");
}
}
if ((f != stdout) && (f != stderr)) fclose(f);
if (state->buf_overruns >= 0) state->buf_overruns = 0; //! resets overruns after reading status , so "overruns" means since last reading status
state->last_error_code = 0; //! Reset error
state->buf_min = _b_free;
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
FOR_EACH_PORT(int, chn) {state->buf_min[chn] = _b_free[chn];}
}
//! will read from pipe, return pointer to null terminated string if available, NULL otherwise
/**
* @brief Read a single command from pipe
* @param[in] npipe pointer to command pipe
* @return Pointer to null terminated string if a command is available or NULL otherwise
*/
char * getLineFromPipe(FILE* npipe)
{
int fl;
char * nlp;
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
//!remove used string if any
//remove used string if any
if (cmdstrt > 0) {
//!moving overlapping strings
//moving overlapping strings
memmove(cmdbuf, &cmdbuf[cmdstrt], sizeof(cmdbuf) - cmdstrt);
cmdbufp -= cmdstrt;
cmdstrt = 0;
}
//! is there any complete string in a buffer?
if (!cmdbufp) cmdbuf[cmdbufp] = 0; //!null-terminate first access (probably not needed for the static buffer
// nlp= strchr(cmdbuf,'\n');
// is there any complete string in a buffer?
if (!cmdbufp) cmdbuf[cmdbufp] = 0; //null-terminate first access (probably not needed for the static buffer
nlp = strpbrk(cmdbuf, ";\n");
if (!nlp) { //!no complete string, try to read more
if (!nlp) { //no complete string, try to read more
fl = fread(&cmdbuf[cmdbufp], 1, sizeof(cmdbuf) - cmdbufp - 1, npipe);
cmdbuf[cmdbufp + fl] = 0;
//! is there any complete string in a buffer after reading?
// nlp= strchr(&cmdbuf[cmdbufp],'\n'); //! there were no new lines before cmdbufp
nlp = strpbrk(&cmdbuf[cmdbufp], ";\n"); //! there were no new lines before cmdbufp
cmdbufp += fl; //!advance pointer after pipe read
// is there any complete string in a buffer after reading?
nlp = strpbrk(&cmdbuf[cmdbufp], ";\n"); // there were no new lines before cmdbufp
cmdbufp += fl; //advance pointer after pipe read
}
if (nlp) {
//printf ("++nlp=%d\n", (int) (nlp-cmdbuf));
nlp[0] = 0;
cmdstrt = nlp - cmdbuf + 1;
//printf ("++cmdstrt=%d\n", cmdstrt);
//printf ("cmdbuf[0]=%d, cmdbuf[1]=%d, cmdbuf[2]=%d, cmdbuf[3]=%d, \n",cmdbuf[0],cmdbuf[1],cmdbuf[2],cmdbuf[3]);
for (fl = 0; cmdbuf[fl] && strchr(" \t", cmdbuf[fl]); fl++) ;
//printf ("++fl=%d\n", fl);
return &cmdbuf[fl];
} else {
//printf ("notready: cmdbufp=%d, cmdstrt=%d\n",cmdbufp, cmdstrt);
return NULL;
}
}
// command[= \t]*args[ \t]*
/**
* @brief Read and execute commands sent over command pipe
* @param state pointer to a structure containing current state
* @param npipe pointer to command pipe
* @return 0 if pipe was empty, positive value corresponding to a command processed or
* -1 in case of an error
*/
int parse_cmd(camogm_state *state, FILE* npipe)
{
char * cmd;
......@@ -1116,35 +1185,33 @@ int parse_cmd(camogm_state *state, FILE* npipe)
int d;
double dd;
// if (!((cmd=getLineFromPipe(npipe)))) return 0; //! nothing in the pipe
//!skip empty commands
//skip empty commands
while (((cmd = getLineFromPipe(npipe))) && !cmd[0]) ;
if (!cmd) return 0; //! nothing in the pipe
if (!cmd) return 0; // nothing in the pipe
D2(fprintf(debug_file, "Got command: '%s'\n", cmd));
/// Acknowledge received command by copying frame number to per-daemon parameter
#ifdef DISABLE_CODE
// Acknowledge received command by copying frame number to per-daemon parameter
// GLOBALPARS(state->port_num, G_DAEMON_ERR + lastDaemonBit[state->port_num]) = GLOBALPARS(state->port_num, G_THIS_FRAME);
setGValue(state->port, G_DAEMON_ERR + lastDaemonBit[state->port_nun], getGValue(state->port_num, G_THIS_FRAME));
setGValue(state->port_num, G_DAEMON_ERR + lastDaemonBit[state->port_num], getGPValue(state->port_num, G_THIS_FRAME));
#endif /* DISABLE_CODE */
// printf ("cmd[0]=%d:%s\n",(int) cmd[0],cmd);
args = strpbrk(cmd, "= \t");
//! is it just a single word command or does it have parameters?
// is it just a single word command or does it have parameters?
if (args) {
args[0] = 0;
args++;
while (strchr("= \t", args[0])) args++;
if (args[0]) {
//! ltrim (args)
// ltrim (args)
for (argse = strchr(args, '\0') - 1; strchr("= \t", argse[0]); argse--) argse[0] = '\0';
}
if (!args[0]) args = NULL;
}
//! now cmd is trimmed, arg is NULL or a pointer to trimmed command arguments
// now cmd is trimmed, arg is NULL or a pointer to trimmed command arguments
if (strcmp(cmd, "start") == 0) {
camogm_start(state);
return 1;
} else if (strcmp(cmd, "reset") == 0) { //! will reset pointer to the last acquired frame (if any)
} else if (strcmp(cmd, "reset") == 0) { // will reset pointer to the last acquired frame (if any)
camogm_reset(state);
return 2;
} else if (strcmp(cmd, "stop") == 0) {
......@@ -1153,6 +1220,7 @@ int parse_cmd(camogm_state *state, FILE* npipe)
} else if (strcmp(cmd, "exit") == 0) {
camogm_stop(state);
camogm_free(state);
clean_up(state);
exit(0);
} else if (strcmp(cmd, "duration") == 0) {
if (!(args) || (((d = strtol(args, NULL, 10))) <= 0)) d = DEFAULT_DURATION;
......@@ -1163,7 +1231,7 @@ int parse_cmd(camogm_state *state, FILE* npipe)
camogm_set_segment_length(state, d);
return 5;
} else if (strcmp(cmd, "prefix") == 0) {
if (args) camogm_set_prefix(state, args);
if (args) camogm_set_prefix(state, args, FILE_PATH);
return 6;
} else if (strcmp(cmd, "status") == 0) {
camogm_status(state, args, 0);
......@@ -1184,15 +1252,15 @@ int parse_cmd(camogm_state *state, FILE* npipe)
dd = strtod(args, NULL);
camogm_set_timescale(state, dd ? dd : 1.0);
return 10;
//!TODO: fix period calculation/check for frame skipping (just disable in frame skip mode?)
//!TODO: add time period (system clock), not just frame skipping
//TODO: fix period calculation/check for frame skipping (just disable in frame skip mode?)
//TODO: add time period (system clock), not just frame skipping
} else if (strcmp(cmd, "frameskip") == 0) {
d = strtol(args, NULL, 10);
camogm_set_frames_skip(state, d);
return 11;
} else if (strcmp(cmd, "timelapse") == 0) { //! period (in seconds) between stored frames
} else if (strcmp(cmd, "timelapse") == 0) { // period (in seconds) between stored frames
d = strtol(args, NULL, 10);
camogm_set_frames_skip(state, -d);
return 11;
......@@ -1257,73 +1325,66 @@ int parse_cmd(camogm_state *state, FILE* npipe)
dd = strtod(args, NULL);
camogm_set_ignore_fps(state, dd);
return 25;
} else if (strcmp(cmd, "port_enable") == 0) {
d = strtol(args, NULL, 10);
set_chn_state(state, d, 1);
return 26;
} else if (strcmp(cmd, "port_disable") == 0) {
d = strtol(args, NULL, 10);
set_chn_state(state, d, 0);
return 27;
} else if (strcmp(cmd, "rawdev_path") == 0) {
if (args) {
camogm_set_prefix(state, args, RAW_PATH);
} else {
state->rawdev_op = 0;
state->rawdev.rawdev_path[0] = '\0';
}
return 28;
} else if (strcmp(cmd, "reader_stop") == 0) {
if (state->prog_state == STATE_READING &&
state->rawdev.thread_state == STATE_RUNNING) {
state->rawdev.thread_state = STATE_CANCEL;
} else {
D0(fprintf(debug_file, "Reading thread is not running, nothing to stop\n"));
}
return -1;
}
/**
* @brief Create a list of pipe names from a single name given
*
* This function accepts a pipe name string provided by the user to the program and
* creates a list of #SENSOR_PORTS similar names but with port number added to the end of
* the name.
* @param[in] pipe_name pipe name provided by user
* @param[out] names a list of names with port number added
* return 0 if the list was successfully created or -1 otherwise
*/
int create_pipe_names(const char *pipe_name, char **names)
{
int ret = 0;
unsigned int len = strlen(pipe_name);
for (int i = 0; i < SENSOR_PORTS; i++) {
char *name = malloc(PATH_MAX);
if (name == NULL) {
ret = -1;
break;
}
strncpy(name, pipe_name, PATH_MAX - 1);
snprintf(&name[len], PATH_MAX - len - 1, "%u", i);
names[i] = name;
}
if (ret < 0) {
for (int i = 0; i < SENSOR_PORTS; i++) {
free(names[i]);
names[i] = NULL;
}
return 29;
}
return ret;
return -1;
}
/**
* @brief This function closes open files and deletes allocated memory.
* @brief This function closes open files, terminates reading thread and deletes allocated memory.
* @param[in] state pointer to #camogm_state structure for a particular sensor channel
* return none
* return None
*/
void clean_up(camogm_state *state)
{
if (state->fd_exif > 0)
close(state->fd_exif);
if (state->fd_head > 0)
close(state->fd_head);
if (state->fd_circ > 0)
close(state->fd_circ);
if (state->fd_fparmsall)
close(state->fd_fparmsall);
free(state->pipe_name);
state->pipe_name = NULL;
for (int port = 0; port < SENSOR_PORTS; port++) {
if (is_fd_valid(state->fd_exif[port]))
close(state->fd_exif[port]);
if (is_fd_valid(state->fd_head[port]))
close(state->fd_head[port]);
if (is_fd_valid(state->fd_circ[port]))
close(state->fd_circ[port]);
if (is_fd_valid(state->fd_fparmsall[port]))
close(state->fd_fparmsall[port]);
}
if (state->rawdev.thread_state != STATE_STOPPED) {
pthread_cancel(state->rawdev.tid);
}
}
/**
* @brief Main processing loop
*
* If recording is on, this function will check for new commands in command pipe after each frame. If recording is
* turn off, it will poll command pipe each #COMMAND_LOOP_DELAYS microseconds and then sleep.
* turn off, it will poll command pipe each #COMMAND_LOOP_DELAY microseconds and then sleep.
* @param[in] state #camogm_state structure associated with a single port
* @return normally this function loops indefinitely processing commands but will return negative exit code in case
* of error and \e EXIT_SUCCESS it eventually terminates in normal way.
* of error and @e EXIT_SUCCESS it eventually terminates in normal way.
*/
int listener_loop(camogm_state *state)
{
......@@ -1331,70 +1392,9 @@ int listener_loop(camogm_state *state)
int rslt, ret, cmd, f_ok;
int fp0, fp1;
int process = 1;
unsigned int port = state->port_num;
int curr_port = 0;
const char *pipe_name = state->pipe_name;
// open Exif header file
#ifdef DISABLE_CODE
state->fd_exif = open(exifFileNames[port], O_RDONLY);
if (state->fd_exif < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", exifFileNames[port]));
clean_up(state);
return -1;
}
#endif /* DESABLE_CODE */
// open JPEG header file
state->fd_head = open(headFileNames[port], O_RDWR);
if (state->fd_head < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", headFileNames[port]));
clean_up(state);
return -1;
}
state->head_size = lseek(state->fd_head, 0, SEEK_END);
if (state->head_size > JPEG_HEADER_MAXSIZE) {
D0(fprintf(debug_file, "%s:%d: Too big JPEG header (%d > %d)", __FILE__, __LINE__, state->head_size, JPEG_HEADER_MAXSIZE ));
clean_up(state);
return -2;
}
// open circbuf and mmap it (once at startup)
state->fd_circ = open(circbufFileNames[port], O_RDWR);
if (state->fd_circ < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", circbufFileNames[port]));
clean_up(state);
return -2;
}
// find total buffer length (it is in defines, actually in c313a.h
state->circ_buff_size = lseek(state->fd_circ, 0, SEEK_END);
ccam_dma_buf[port] = (unsigned long*)mmap(0, state->circ_buff_size, PROT_READ, MAP_SHARED, state->fd_circ, 0);
if ((int)ccam_dma_buf[port] == -1) {
D0(fprintf(debug_file, "Error in mmap of %s\n", circbufFileNames[port]));
clean_up(state);
return -3;
}
// now open/mmap file to read sensor/compressor parameters (currently - just free memory in circbuf and compressor state)
#ifdef DISABLE_CODE
state->fd_fparmsall = open(ctlFileNames[port], O_RDWR);
if (state->fd_fparmsall < 0) { // check control OK
D0(fprintf(debug_file, "%s:%d:%s: Error opening %s\n", __FILE__, __LINE__, __FUNCTION__, ctlFileNames[port]));
clean_up(state);
return -2;
}
// now try to mmap
// PROT_WRITE - only to write acknowledge
frameParsAll[port] = (struct framepars_all_t*)mmap(0, sizeof(struct framepars_all_t), PROT_READ | PROT_WRITE, MAP_SHARED, state->fd_fparmsall, 0);
if ((int)frameParsAll[port] == -1) {
D0(fprintf(debug_file, "%s:%d:%s: Error in mmap in %s\n", __FILE__, __LINE__, __FUNCTION__, ctlFileNames[port]));
clean_up(state);
return -3;
}
framePars[port] = frameParsAll[port]->framePars;
globalPars[port] = frameParsAll[port]->globalPars;
#endif /* DESABLE_CODE */
// create a named pipe
// always delete the pipe if it existed, start a fresh one
f_ok = access(pipe_name, F_OK);
......@@ -1425,24 +1425,25 @@ int listener_loop(camogm_state *state)
// enter main processing loop
while (process) {
curr_port = select_port(state);
state->port_num = curr_port;
// look at command queue first
cmd = parse_cmd(state, cmd_file);
if (cmd) {
if (cmd < 0) D0(fprintf(debug_file, "Unrecognized command\n"));
} else if (state->running) { // no commands in queue, started
} else if (state->prog_state == STATE_RUNNING) { // no commands in queue, started
switch ((rslt = -sendImageFrame(state))) {
case 0:
break; // frame sent OK, nothing to do (TODO: check file length/duration)
case CAMOGM_FRAME_NOT_READY: // just wait for the frame to appear at the current pointer
// we'll wait for a frame, not to waste resources. But if the compressor is stopped this program will not respond to any commands
// TODO - add another wait with (short) timeout?
fp0 = lseek(state->fd_circ, 0, SEEK_CUR);
fp0 = lseek(state->fd_circ[curr_port], 0, SEEK_CUR);
if (fp0 < 0) {
D0(fprintf(debug_file, "%s:line %d got broken frame (%d) before waiting for ready\n", __FILE__, __LINE__, fp0));
rslt = CAMOGM_FRAME_BROKEN;
} else {
fp1 = lseek(state->fd_circ, LSEEK_CIRC_WAIT, SEEK_END);
fp1 = lseek(state->fd_circ[curr_port], LSEEK_CIRC_WAIT, SEEK_END);
if (fp1 < 0) {
D0(fprintf(debug_file, "%s:line %d got broken frame (%d) while waiting for ready. Before that fp0=0x%x\n", __FILE__, __LINE__, fp1, fp0));
rslt = CAMOGM_FRAME_BROKEN;
......@@ -1471,15 +1472,15 @@ int listener_loop(camogm_state *state)
} // switch sendImageFrame()
if ((rslt != 0) && (rslt != CAMOGM_FRAME_NOT_READY) && (rslt != CAMOGM_FRAME_CHANGED)) state->last_error_code = rslt;
} else if (state->starting) { // no commands in queue,starting (but not started yet)
} else if (state->prog_state == STATE_STARTING) { // no commands in queue,starting (but not started yet)
// retry starting
switch ((rslt = -camogm_start(state))) {
case 0:
break; // file started OK, nothing to do
case CAMOGM_TOO_EARLY:
lseek(state->fd_circ, LSEEK_CIRC_TOWP, SEEK_END); // set pointer to the frame to wait for
lseek(state->fd_circ, LSEEK_CIRC_WAIT, SEEK_END); // It already passed CAMOGM_FRAME_NOT_READY, so compressor may be running already
lseek(state->fd_circ[curr_port], LSEEK_CIRC_TOWP, SEEK_END); // set pointer to the frame to wait for
lseek(state->fd_circ[curr_port], LSEEK_CIRC_WAIT, SEEK_END); // It already passed CAMOGM_FRAME_NOT_READY, so compressor may be running already
break; // no need to wait extra
case CAMOGM_FRAME_NOT_READY: // just wait for the frame to appear at the current pointer
// we'll wait for a frame, not to waste resources. But if the compressor is stopped this program will not respond to any commands
......@@ -1500,8 +1501,10 @@ int listener_loop(camogm_state *state)
exit(-1);
} // switch camogm_start()
if ((rslt != 0) && (rslt != CAMOGM_TOO_EARLY) && (rslt != CAMOGM_FRAME_NOT_READY) && (rslt != CAMOGM_FRAME_CHANGED) ) state->last_error_code = rslt;
} else if (state->prog_state == STATE_READING) {
usleep(COMMAND_LOOP_DELAY);
} else { // not running, not starting
state->rawdev.thread_state = STATE_RUNNING;
usleep(COMMAND_LOOP_DELAY); // make it longer but interruptible by signals?
}
} // while (process)
......@@ -1511,16 +1514,172 @@ int listener_loop(camogm_state *state)
return EXIT_SUCCESS;
}
/**
* @brief Return total disk size in bytes
*
* This function reads disk size using ioctl call and returns it in bytes.
* @param name pointer to disk name string
* @return disk size in bytes if it was read correctly and 0 otherwise
*/
uint64_t get_disk_size(const char *name)
{
int fd;
uint64_t dev_sz;
if ((fd = open(name, O_RDONLY)) < 0) {
perror(__func__);
return 0;
}
if (ioctl(fd, BLKGETSIZE64, &dev_sz) < 0) {
perror(__func__);
return 0;
}
close(fd);
return dev_sz;
}
/**
* @brief Select a sensor channel with minimum free space left in the buffer. The channel will
* be selected from the list of active channels.
* @param[in] state a pointer to a structure containing current state
* @return The number of a channel with minimum free space left. This function
* will return 0 in case all channels are disabled.
*/
unsigned int select_port(camogm_state *state)
{
unsigned int chn = 0;
off_t free_sz;
off_t file_pos;
off_t min_sz = -1;
for (int i = 0; i < SENSOR_PORTS; i++) {
if (is_chn_active(state, i)) {
file_pos = lseek(state->fd_circ[i], 0, SEEK_CUR);
if (file_pos != -EINVAL) {
free_sz = lseek(state->fd_circ[i], LSEEK_CIRC_FREE, SEEK_END);
lseek(state->fd_circ[i], file_pos, SEEK_SET);
if ((free_sz < min_sz && free_sz >= 0) || min_sz == -1) {
min_sz = free_sz;
chn = i;
}
}
}
}
return chn;
}
/**
* @brief Check if channel is enabled or disabled
* @param[in] s a pointer to a structure containing current state
* @param[in] port a port which should be checked
* @return 0 if the channel is disabled and 1 otherwise
*/
inline int is_chn_active(camogm_state *s, unsigned int port)
{
return (s->active_chn >> port) & 0x1;
}
/**
* @brief Change the state of a given sensor channel.
* @param[in,out] s a pointer to a structure containing current state
* @param[in] port port which state should be changed
* @param[in] new_state new state of the channel
* @return None
*/
inline void set_chn_state(camogm_state *s, unsigned int port, unsigned int new_state)
{
if (port >= 0 && port < SENSOR_PORTS) {
if (new_state)
s->active_chn |= 1 << port;
else
s->active_chn &= ~(1 << port);
}
}
/**
* @brief Open files provided by circbuf driver.
* @param[in,out] state a pointer to a structure containing current state
* @return 0 on success or negative error code
*/
int open_files(camogm_state *state)
{
int ret = 0;
for (int port = 0; port < SENSOR_PORTS; port++) {
// open Exif header file
state->fd_exif[port] = open(exifFileNames[port], O_RDONLY);
if (state->fd_exif[port] < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", exifFileNames[port]));
clean_up(state);
return -1;
}
// open JPEG header file
state->fd_head[port] = open(headFileNames[port], O_RDWR);
if (state->fd_head[port] < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", headFileNames[port]));
clean_up(state);
return -1;
}
state->head_size[port] = lseek(state->fd_head[port], 0, SEEK_END);
if (state->head_size[port] > JPEG_HEADER_MAXSIZE) {
D0(fprintf(debug_file, "%s:%d: Too big JPEG header (%d > %d)", __FILE__, __LINE__, state->head_size[port], JPEG_HEADER_MAXSIZE ));
clean_up(state);
return -2;
}
// open circbuf and mmap it (once at startup)
state->fd_circ[port] = open(circbufFileNames[port], O_RDWR);
if (state->fd_circ < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", circbufFileNames[port]));
clean_up(state);
return -2;
}
// find total buffer length (it is in defines, actually in c313a.h
state->circ_buff_size[port] = lseek(state->fd_circ[port], 0, SEEK_END);
ccam_dma_buf[port] = (unsigned long*)mmap(0, state->circ_buff_size[port], PROT_READ, MAP_SHARED, state->fd_circ[port], 0);
if ((int)ccam_dma_buf[port] == -1) {
D0(fprintf(debug_file, "Error in mmap of %s\n", circbufFileNames[port]));
clean_up(state);
return -3;
}
// now open/mmap file to read sensor/compressor parameters (currently - just free memory in circbuf and compressor state)
#ifdef DISABLE_CODE
state->fd_fparmsall[port] = open(ctlFileNames[port], O_RDWR);
if (state->fd_fparmsall[port] < 0) { // check control OK
D0(fprintf(debug_file, "%s:%d:%s: Error opening %s\n", __FILE__, __LINE__, __FUNCTION__, ctlFileNames[port]));
clean_up(state);
return -2;
}
// now try to mmap
// PROT_WRITE - only to write acknowledge
frameParsAll[port] = (struct framepars_all_t*)mmap(0, sizeof(struct framepars_all_t), PROT_READ | PROT_WRITE, MAP_SHARED, state->fd_fparmsall[port], 0);
if ((int)frameParsAll[port] == -1) {
D0(fprintf(debug_file, "%s:%d:%s: Error in mmap in %s\n", __FILE__, __LINE__, __FUNCTION__, ctlFileNames[port]));
clean_up(state);
return -3;
}
framePars[port] = frameParsAll[port]->framePars;
aglobalPars[port] = frameParsAll[port]->globalPars;
#endif /* DISABLE_CODE */
}
return ret;
}
int main(int argc, char *argv[])
{
const char usage[] = "This program allows recording of the video/images acquired by Elphel camera to the storage media.\n" \
"It is designed to run in the background and accept commands through a named pipe.\n\n" \
"It is designed to run in the background and accept commands through a named pipe or a socket.\n\n" \
"Usage:\n\n" \
"%s <named_pipe_name>\n\n" \
"%s -n <named_pipe_name> -p <port_number>\n\n" \
"i.e.:\n\n" \
"%s /var/state/camogm_cmd\n\n" \
"When the program is runninig you may send commands by writing strings to the command file\n" \
"(/var/state/camogm_cmd in the example above). The complete list of available commands is available\n" \
"%s -n /var/state/camogm_cmd -p 1234\n\n" \
"When the program is running you may send commands by writing strings to the command file\n" \
"(/var/state/camogm_cmd in the example above) or to the socket. The complete list of available commands is available\n" \
"on Elphel Wiki (http://wiki.elphel.com/index.php?title=Camogm), here is the example of usage\n" \
"from the shell prompt in the camera:\n\n" \
"echo \"status; exif=1; format=jpeg;status=/var/tmp/camogm.status\" > /var/state/camogm_cmd\n\n" \
......@@ -1531,30 +1690,48 @@ int main(int argc, char *argv[])
"This program does not control the process of acquisition of the video/images to the camera internal\n" \
"buffer, it only retrieves that data from the buffer (waiting when needed), packages it to selected\n" \
"format and stores the result files.\n\n";
char *pipe_names[SENSOR_PORTS] = {0};
int ret;
int opt;
uint16_t port_num = 0;
char pipe_name_str[ELPHEL_PATH_MAX] = {0};
// no command line options processing yet
if ((argc < 2) || (argv[1][1] == '-')) {
if ((argc < 5) || (argv[1][1] == '-')) {
printf(usage, argv[0], argv[0]);
return EXIT_SUCCESS;
}
while ((opt = getopt(argc, argv, "n:p:h")) != -1) {
switch (opt) {
case 'n':
strncpy(pipe_name_str, (const char *)optarg, ELPHEL_PATH_MAX - 1);
break;
case 'p':
port_num = (uint16_t)atoi((const char *)optarg);
break;
case 'h':
printf(usage, argv[0], argv[0]);
return EXIT_SUCCESS;
}
if (create_pipe_names(argv[1], pipe_names) < 0) {
printf("Error: unable to allocate memory for command pipe name\n");
return EXIT_FAILURE;
}
// spawn a process for each sensor port
for (int i = 0; i < SENSOR_PORTS; i++) {
camogm_init(&sstate[i], i, pipe_names[i]);
if (fork() == 0) {
ret = listener_loop(&sstate[i]);
exit(ret);
camogm_init(&sstate, pipe_name_str, port_num);
if (pthread_mutex_init(&sstate.mutex, NULL) != 0) {
perror("Unable to initialize mutex\n");
return EXIT_FAILURE;
}
ret = open_files(&sstate);
if (ret < 0)
return ret;
if (pthread_create(&sstate.rawdev.tid, NULL, reader, &sstate) != 0 ||
pthread_detach(sstate.rawdev.tid) != 0) {
D0(fprintf(debug_file, "%s:line %d: Can not start reading thread in detached state\n", __FILE__, __LINE__));
clean_up(&sstate);
return EXIT_FAILURE;
}
sstate.rawdev.thread_state = STATE_RUNNING;
return EXIT_SUCCESS;
ret = listener_loop(&sstate);
return ret;
}
/**
......@@ -1576,9 +1753,10 @@ unsigned long getGPValue(unsigned int port, unsigned long GPNumber)
/**
* @brief Set value of the specified global (G_*) parameter
* @param[in] port sensor port number
* @param[in] GNumber parameter number (as defined in c313a.h)
* @param[in] value value to set
* @return none
* @return None
*/
void setGValue(unsigned int port, unsigned long GNumber, unsigned long value)
{
......@@ -1588,23 +1766,25 @@ void setGValue(unsigned int port, unsigned long GNumber, unsigned long value)
/**
* @brief Check if this application is enabled (by appropriate bit in P_DAEMON_EN), if not -
* and wait until enabled (return false when enabled)
* @param[in] port sensor port number
* @param[in] daemonBit bit number to accept control in P_DAEMON_EN parameter
* @return (after possible waiting) true if there was no waiting, false if there was waiting
*/
int waitDaemonEnabled(unsigned int port, int daemonBit) // <0 - use default
{
camogm_state *state = &sstate[port];
camogm_state *state = &sstate;
if ((daemonBit >= 0) && (daemonBit < 32)) lastDaemonBit[port] = daemonBit;
unsigned long this_frame = GLOBALPARS(port, G_THIS_FRAME);
// No semaphors, so it is possible to miss event and wait until the streamer will be re-enabled before sending message,
// but it seems not so terrible
lseek(state->fd_circ, LSEEK_DAEMON_CIRCBUF + lastDaemonBit[port], SEEK_END); ///
lseek(state->fd_circ[state->port_num], LSEEK_DAEMON_CIRCBUF + lastDaemonBit[port], SEEK_END);
if (this_frame == GLOBALPARS(port, G_THIS_FRAME)) return 1;
return 0;
}
/**
* @brief Check if this application is enabled (by appropriate bit in P_DAEMON_EN)
* @param[in] port sensor port number
* @param[in] daemonBit bit number to accept control in P_DAEMON_EN parameter
* @return (after possible waiting) true if there was no waiting, false if there was waiting
*/
......@@ -1613,3 +1793,14 @@ int isDaemonEnabled(unsigned int port, int daemonBit) // <0 - use default
if ((daemonBit >= 0) && (daemonBit < 32)) lastDaemonBit[port] = daemonBit;
return ((framePars[port][GLOBALPARS(port, G_THIS_FRAME) & PARS_FRAMES_MASK].pars[P_DAEMON_EN] & (1 << lastDaemonBit[port])) != 0);
}
/**
* @brief Check if file descriptor is valid. This function is used to check if file
* is opened before closing it.
* @param[in] fd file descriptor to check
* @return 1 if descriptor is valid and 0 otherwise
*/
inline int is_fd_valid(int fd)
{
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
/** @file camogm.h
* @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/>.
*/
#ifndef _CAMOGM_H
#define _CAMOGM_H
#define CAMOGM_FRAME_NOT_READY 1 // frame pointer valid, but not yet acquired
#define CAMOGM_FRAME_INVALID 2 // invalid frame pointer
#define CAMOGM_FRAME_CHANGED 3 // frame parameters have changed
#define CAMOGM_FRAME_NEXTFILE 4 // need to switch to a new file segment
#define CAMOGM_FRAME_BROKEN 5 // frame broken (buffer overrun)
#define CAMOGM_FRAME_FILE_ERR 6 // error with file I/O
#define CAMOGM_FRAME_MALLOC 7 // can not allocate memory
#define CAMOGM_TOO_EARLY 8 // too early to start, waiting for particular timestamp
#define CAMOGM_FRAME_OTHER 9 // other errors
#define CAMOGM_FORMAT_NONE 0 // no video output
#define CAMOGM_FORMAT_OGM 1 // output as Ogg Media file
#define CAMOGM_FORMAT_JPEG 2 // output as individual JPEG files
#define CAMOGM_FORMAT_MOV 3 // output as Apple Quicktime
#include <pthread.h>
#include <stdbool.h>
#include <exifa.h>
#include <c313a.h>
#include <ogg/ogg.h>
#include "ogmstreams.h"
#define CAMOGM_FRAME_NOT_READY 1 ///< frame pointer valid, but not yet acquired
#define CAMOGM_FRAME_INVALID 2 ///< invalid frame pointer
#define CAMOGM_FRAME_CHANGED 3 ///< frame parameters have changed
#define CAMOGM_FRAME_NEXTFILE 4 ///< need to switch to a new file segment
#define CAMOGM_FRAME_BROKEN 5 ///< frame broken (buffer overrun)
#define CAMOGM_FRAME_FILE_ERR 6 ///< error with file I/O
#define CAMOGM_FRAME_MALLOC 7 ///< can not allocate memory
#define CAMOGM_TOO_EARLY 8 ///< too early to start, waiting for particular timestamp
#define CAMOGM_FRAME_OTHER 9 ///< other errors
#define CAMOGM_NO_SPACE 10 ///< no free space left on current file system
#define CAMOGM_FORMAT_NONE 0 ///< no video output
#define CAMOGM_FORMAT_OGM 1 ///< output as Ogg Media file
#define CAMOGM_FORMAT_JPEG 2 ///< output as individual JPEG files
#define CAMOGM_FORMAT_MOV 3 ///< output as Apple Quicktime
#define D(x) { if (debug_file && debug_level) { x; fflush(debug_file); } }
#define D0(x) { if (debug_file) { x; fflush(debug_file); } }
#define D1(x) { if (debug_file && (debug_level > 0)) { x; fflush(debug_file); } }
#define D2(x) { if (debug_file && (debug_level > 1)) { x; fflush(debug_file); } }
#define D3(x) { if (debug_file && (debug_level > 2)) { x; fflush(debug_file); } }
#define D4(x) { if (debug_file && (debug_level > 3)) { x; fflush(debug_file); } }
#define D5(x) { if (debug_file && (debug_level > 4)) { x; fflush(debug_file); } }
#define D6(x) { if (debug_file && (debug_level > 5)) { x; fflush(debug_file); } }
#define D0(x) { if (debug_file) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
#define D1(x) { if (debug_file && (debug_level > 0)) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
#define D2(x) { if (debug_file && (debug_level > 1)) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
#define D3(x) { if (debug_file && (debug_level > 2)) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
#define D4(x) { if (debug_file && (debug_level > 3)) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
#define D5(x) { if (debug_file && (debug_level > 4)) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
#define D6(x) { if (debug_file && (debug_level > 5)) { pthread_mutex_lock(&print_mutex); x; fflush(debug_file); pthread_mutex_unlock(&print_mutex); } }
//#define DD(x)
#define DD(x) { if (debug_file) { fprintf(debug_file, "%s:%d:", __FILE__, __LINE__); x; fflush(debug_file); } }
// HEADER_SIZE is defined to be larger than actual header (later - with EXIF) to use compile-time buffer
#define JPEG_HEADER_MAXSIZE 0x300 // will not change
//#include "camogm_exif.h"
#include <exifa.h>
/** @brief HEADER_SIZE is defined to be larger than actual header (with EXIF) to use compile-time buffer */
#define JPEG_HEADER_MAXSIZE 0x300
/** @brief Offset from the beginning of raw device buffer. Must be aligned to physical sector size */
#define RAWDEV_START_OFFSET 1024
/** @brief Maximum length of file or raw device path */
#define ELPHEL_PATH_MAX 300
#define MMAP_CHUNK_SIZE 10485760
/** @brief Time interval (in microseconds) for processing commands */
#define COMMAND_LOOP_DELAY 500000
/**
* @enum state_flags
* @brief Program state flags
*/
enum state_flags {
STATE_STOPPED,
STATE_STARTING,
STATE_RUNNING,
STATE_READING,
STATE_CANCEL
};
/*
#define Exif_Photo_DateTimeOriginal 0x19003
#define Exif_GPSInfo_GPSLatitudeRef 0x20001
#define Exif_GPSInfo_GPSLatitude 0x20002
#define Exif_GPSInfo_GPSLongitudeRef 0x20003
#define Exif_GPSInfo_GPSLongitude 0x20004
#define Exif_GPSInfo_GPSAltitudeRef 0x20005
#define Exif_GPSInfo_GPSAltitude 0x20006
#define Exif_GPSInfo_GPSTimeStamp 0x20007
#define Exif_GPSInfo_GPSDateStamp 0x2001D
#define Exif_GPSInfo_CompassDirectionRef 0x20010
#define Exif_GPSInfo_CompassDirection 0x20011
#define Exif_GPSInfo_CompassPitchRef 0x20013
#define Exif_GPSInfo_CompassPitch 0x20014
#define Exif_GPSInfo_CompassRollRef 0x20015
#define Exif_GPSInfo_CompassRoll 0x20016
/**
* @struct rawdev_buffer
* @brief Holds pointers related to raw device buffer operation
* @var rawdev_buffer::rawdev_fd
* File descriptor of open raw device
* @var rawdev_buffer::rawdev_path
* A string containing full path to raw device
* @var rawdev_buffer::overrun
* The number of times the buffer has overrun during current work session
* @var rawdev_buffer::start_pos
* The start position of raw device buffer
* @var rawdev_buffer::end_pos
* The end position of raw device buffer
* @var rawdev_buffer::curr_pos_r
* Current read position in raw device buffer
* @var rawdev_buffer::curr_pos_w
* Current write position in raw device buffer
* @var rawdev_buffer::mmap_default_size
* The default size of memory mapped disk region
* @var rawdev_buffer::mmap_current_size
* The size of currently memory mapped disk region. Can be less then #mmap_default_size
* @var rawdev_buffer::mmap_offset
* Current offset (in bytes) from the beginning of raw device buffer
* @var rawdev_buffer::file_start
* Pointer to the beginning of current file. This pointer is set during raw device reading and
* updated every time new file is found.
* @var rawdev_buffer::tid
* The ID of raw device reading thread
* @var rawdev_buffer::thread_state
* The state of the reading thread. Used to interrupt current operation
* @var rawdev_buffer::disk_mmap
* Pointer to memory mapped buffer region
*/
/*
/// Exif data (variable, stored with each frame) used for KML (not only)
#define Exif_Image_ImageDescription_Index 0x00
#define Exif_Photo_DateTimeOriginal_Index 0x01
#define Exif_Photo_SubSecTimeOriginal_Index 0x02
#define Exif_GPSInfo_GPSLatitudeRef_Index 0x03
#define Exif_GPSInfo_GPSLatitude_Index 0x04
#define Exif_GPSInfo_GPSLongitudeRef_Index 0x05
#define Exif_GPSInfo_GPSLongitude_Index 0x06
#define Exif_GPSInfo_GPSAltitudeRef_Index 0x07
#define Exif_GPSInfo_GPSAltitude_Index 0x08
#define Exif_GPSInfo_GPSTimeStamp_Index 0x09
#define Exif_GPSInfo_GPSDateStamp_Index 0x0a
#define Exif_GPSInfo_CompassDirectionRef_Index 0x0b
#define Exif_GPSInfo_CompassDirection_Index 0x0c
#define Exif_GPSInfo_CompassPitchRef_Index 0x0d
#define Exif_GPSInfo_CompassPitch_Index 0x0e
#define Exif_GPSInfo_CompassRollRef_Index 0x0f
#define Exif_GPSInfo_CompassRoll_Index 0x10
#define ExifKmlNumber 0x11
typedef struct {
int rawdev_fd;
char rawdev_path[ELPHEL_PATH_MAX];
uint32_t overrun;
uint64_t start_pos;
uint64_t end_pos;
volatile uint64_t curr_pos_r;
uint64_t curr_pos_w;
uint64_t mmap_default_size;
uint64_t mmap_current_size;
uint64_t mmap_offset;
uint64_t file_start;
pthread_t tid;
volatile int thread_state;
unsigned char *disk_mmap;
} rawdev_buffer;
/**
* @struct camogm_state
* @brief Holds current state of the running program
*/
typedef struct {
int segment_duration;
int segment_length;
int greedy;
int ignore_fps;
int save_gp; //if non zero, current circbuf pointer will be saved to global pointer, so imgsrv can report /pointers
char path_prefix[256];
char path[300];
int cirbuf_rp; //!-1 - invalid
int fd_circ; //! file descriptor for circbuf
int fd_head; //! file descriptor for JPEG header
// int fd_sens; //! file descriptor for sensor/compressor parameters
int fd_fparmsall; //! file descriptor for sensor/compressor parameters
int fd_exif; //! file descriptor for Exif data
int head_size; //! JPEG header size
unsigned char jpegHeader [JPEG_HEADER_MAXSIZE];
int save_gp; ///< if non zero, current circbuf pointer will be saved to global pointer, so imgsrv can report /pointers
char path_prefix[256]; ///< file name prefix
char path[ELPHEL_PATH_MAX]; ///< full file name
int cirbuf_rp[SENSOR_PORTS]; ///< -1 means the pointer is invalid
int fd_circ[SENSOR_PORTS]; ///< file descriptor for circbuf
int fd_head[SENSOR_PORTS]; ///< file descriptor for JPEG header
int fd_fparmsall[SENSOR_PORTS]; ///< file descriptor for sensor/compressor parameters
int fd_exif[SENSOR_PORTS]; ///< file descriptor for Exif data
int head_size[SENSOR_PORTS]; ///< JPEG header size
unsigned char jpegHeader[SENSOR_PORTS][JPEG_HEADER_MAXSIZE];
int metadata_start;
struct interframe_params_t frame_params;
struct interframe_params_t this_frame_params;
struct interframe_params_t frame_params[SENSOR_PORTS];
struct interframe_params_t this_frame_params[SENSOR_PORTS];
int jpeg_len;
int frame_period; //!in microseconds (1/10 of what is needed for the Ogm header)
int width;
int height;
int starting;
int running;
int frame_period[SENSOR_PORTS]; ///< in microseconds (1/10 of what is needed for the Ogm header)
int width; ///< image width
int height; ///< image height
volatile int prog_state; ///< program state flag, can be one of #state_flags
pthread_mutex_t mutex; ///< mutex for @e prog_state variable; all modifications to the variable must be using this mutex
int last_error_code;
ogg_stream_state os;
ogg_page og;
......@@ -105,72 +162,73 @@ typedef struct {
int serialno;
ogg_int64_t packetno;
ogg_int64_t granulepos;
FILE* vf; //! video file (ogm, fopen)
int ivf; //! video file (jpeg, mov - open)
int last; //last packet in a file
int exif; // 1 - calculate and include Exif headers in each frame
// exif_pointers_t ep;
// int exifValid;
int exifSize; //signed
unsigned char ed[MAX_EXIF_SIZE];
int circ_buff_size;
int senspars_size;
FILE* vf; ///< video file (ogm, fopen)
int ivf; ///< video file (jpeg, mov - open)
int last; ///< last packet in a file
int exif; ///< flag indicating that Exif headers should be calculated and included in each frame
int exifSize[SENSOR_PORTS]; ///< signed
unsigned char ed[SENSOR_PORTS][MAX_EXIF_SIZE];
int circ_buff_size[SENSOR_PORTS];
char debug_name[256];
// FILE* debug_file;
int set_samples_per_unit;
double timescale; //! current timescale, default 1.0
double timescale; ///< current timescale, default 1.0
double set_timescale;
double start_after_timestamp; /// delay recording start to after frame timestamp
double start_after_timestamp; ///< delay recording start to after frame timestamp
int max_frames;
int set_max_frames;
int frames_per_chunk;
int set_frames_per_chunk; // quicktime - index for fast forward?
int set_frames_per_chunk; ///< quicktime - index for fast forward?
int frameno;
int* frame_lengths;
off_t frame_data_start; //! Quicktime (and else?) - frame data start (0xff 0xd8...)
int *frame_lengths;
off_t frame_data_start; ///< Quicktime (and else?) - frame data start (0xff 0xd8...)
ogg_int64_t time_unit;
int formats; //! bitmask of used (initialized) formats
int format; //! output file format
int set_format; //! output format to set (will be updated after stop)
int formats; ///< bitmask of used (initialized) formats
int format; ///< output file format
int set_format; ///< output format to set (will be updated after stop)
elph_packet_chunk packetchunks[7];
int chunk_index;
int buf_overruns;
int buf_min;
int set_frames_skip; //! will be copied to frames_skip if stopped or at start
int frames_skip; //! number of frames to skip after the one recorded (for time lapse)
//! if negetive - -(interval between frames in seconds)
int frames_skip_left; //! number of frames left to skip before the next one to be processed
//! if (frames_skip <0) - next timestamp to save an image
//kml stuff
int kml_enable; //! enable KML file generation
int kml_used; //! KML file generation used (change only when stopped)
char kml_path[300]; //! full path for KML file (if any)
FILE* kml_file; //! stream to write kml file
double kml_horHalfFov; //! half horizontal Fov (degrees)
double kml_vertHalfFov; //! half vertical Fov (degrees)
double kml_near; //! Use in KML "near" parameter (<=0 - don't use it)
int kml_height_mode; //! 1 - actual, 0 - ground
double kml_height; //! extra height to add
int kml_period; //! generate PhotoOverlay for each kml_period seconds;
int kml_last_ts; //! last generated kml file timestamp
int kml_last_uts; //! last generated kml file timestamp, microseconds
struct exif_dir_table_t kml_exif[ExifKmlNumber]; //! store locations of the fields needed for KML generations in the Exif block
unsigned int port_num; // sensor port this state assigned to
char *pipe_name; // command pipe name for this sensor port
int buf_overruns[SENSOR_PORTS];
int buf_min[SENSOR_PORTS];
int set_frames_skip; ///< will be copied to frames_skip if stopped or at start
int frames_skip; ///< number of frames to skip after the one recorded (for time lapse)
///< if negative - -(interval between frames in seconds)
int frames_skip_left[SENSOR_PORTS]; ///< number of frames left to skip before the next one to be processed
///< if (frames_skip <0) - next timestamp to save an image
// kml stuff
int kml_enable; ///< enable KML file generation
int kml_used; ///< KML file generation used (change only when stopped)
char kml_path[300]; ///< full path for KML file (if any)
FILE* kml_file; ///< stream to write kml file
double kml_horHalfFov; ///< half horizontal Fov (degrees)
double kml_vertHalfFov; ///< half vertical Fov (degrees)
double kml_near; ///< Use in KML "near" parameter (<=0 - don't use it)
int kml_height_mode; ///< 1 - actual, 0 - ground
double kml_height; ///< extra height to add
int kml_period; ///< generate PhotoOverlay for each kml_period seconds;
int kml_last_ts; ///< last generated kml file timestamp
int kml_last_uts; ///< last generated kml file timestamp, microseconds
struct exif_dir_table_t kml_exif[ExifKmlNumber]; ///< store locations of the fields needed for KML generations in the Exif block
unsigned int port_num; ///< sensor port we are currently working with
char *pipe_name; ///< command pipe name
int rawdev_op; ///< flag indicating writing to raw device
rawdev_buffer rawdev; ///< contains pointers to raw device buffer
unsigned int active_chn; ///< bitmask of active sensor ports
uint16_t sock_port; ///< command socket port number
} camogm_state;
extern int debug_level;
extern FILE* debug_file;
//extern camogm_state * state;
extern pthread_mutex_t print_mutex;
void put_uint16(void *buf, u_int16_t val);
void put_uint32(void *buf, u_int32_t val);
void put_uint64(void *buf, u_int64_t val);
unsigned long getGPValue(unsigned int port, unsigned long GPNumber);
//void setGValue(unsigned long GNumber, unsigned long value);
//int waitDaemonEnabled(int daemonBit); // <0 - use default
//int isDaemonEnabled(int daemonBit); // <0 - use default
void setGValue(unsigned int port, unsigned long GNumber, unsigned long value);
int waitDaemonEnabled(unsigned int port, int daemonBit);
int isDaemonEnabled(unsigned int port, int daemonBit);
int is_fd_valid(int fd);
#endif /* _CAMOGM_H */
/*!***************************************************************************
*! FILE NAME : camogm_jpeg.c
*! DESCRIPTION: Provides writing to series of individual JPEG files for camogm
*! Copyright (C) 2007 Elphel, Inc.
*! -----------------------------------------------------------------------------**
*! 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/>.
*! -----------------------------------------------------------------------------**
*!
*! $Log: camogm_jpeg.c,v $
*! Revision 1.2 2009/02/25 17:50:51 spectr_rain
*! removed deprecated dependency
*!
*! Revision 1.1.1.1 2008/11/27 20:04:01 elphel
*!
*!
*! Revision 1.3 2008/04/11 23:09:33 elphel
*! modified to handle kml generation
*!
*! Revision 1.2 2007/11/19 03:23:21 elphel
*! 7.1.5.5 Added support for *.mov files in camogm.
*!
*! Revision 1.1 2007/11/16 08:49:57 elphel
*! Initial release of camogm - program to record video/image to the camera hard drive (or other storage)
*!
/** @file camogm_jpeg.c
* @brief Provides writing to series of individual JPEG files for @e camogm
* @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/>.
*/
//!Not all are needed, just copied from the camogm.c
/** @brief This define is needed to use lseek64 and should be set before includes */
#define _LARGEFILE64_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
//#include <ctype.h>
//#include <getopt.h>
#include <time.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h> /*little <-> big endian ?*/
#include <sys/mman.h> /* mmap */
#include <sys/ioctl.h>
#include <c313a.h>
#include <asm/byteorder.h>
#include <ogg/ogg.h> // has to be before ogmstreams.h
#include "ogmstreams.h" // move it to <>?
#include "camogm_jpeg.h"
//! may add something - called first time format is changed to this one (only once) recording is stopped
int camogm_init_jpeg(void)
int camogm_init_jpeg(camogm_state *state)
{
return 0;
}
void camogm_free_jpeg(void)
{
}
/**
* @brief Called every time the JPEG files recording is started.
*
* This function checks if the raw device write is initiated and tries to open the device specified. The device
* will be closed in #camogm_end_jpeg function.
* @param[in] state a pointer to a structure containing current state
* @return 0 if the device was opened successfully and negative error code otherwise
*/
int camogm_start_jpeg(camogm_state *state)
{
//!TODO: make directory if it does not exist (find the last "/" in the state->path
char * slash;
int rslt;
strcpy(state->path, state->path_prefix); //!make state->path a directory name (will be replaced when the frames will be written)
if (!state->rawdev_op) {
strcpy(state->path, state->path_prefix); // make state->path a directory name (will be replaced when the frames will be written)
slash = strrchr(state->path, '/');
D2(fprintf(debug_file, "camogm_start_jpeg\n"));
if (slash) {
D3(fprintf(debug_file, "Full path %s\n", state->path));
slash[0] = '\0'; //! truncate path to the directory name
slash[0] = '\0'; // truncate path to the directory name
D3(fprintf(debug_file, "directory path %s\n", state->path));
rslt = mkdir(state->path, 0777);
D3(fprintf(debug_file, "mkdir (%s, 0777) returned %d, errno=%d\n", state->path, rslt, errno));
......@@ -92,32 +67,52 @@ int camogm_start_jpeg(camogm_state *state)
return -CAMOGM_FRAME_FILE_ERR;
}
}
} else {
if (state->rawdev_op) {
state->rawdev.rawdev_fd = open(state->rawdev.rawdev_path, O_RDWR);
if (state->rawdev.rawdev_fd < 0) {
D0(perror(__func__));
D0(fprintf(debug_file, "Error opening raw device %s\n", state->rawdev.rawdev_path));
return -CAMOGM_FRAME_FILE_ERR;
}
D3(fprintf(debug_file, "Open raw device %s; start_pos = %llu, end_pos = %llu, curr_pos = %llu\n", state->rawdev.rawdev_path,
state->rawdev.start_pos, state->rawdev.end_pos, state->rawdev.curr_pos_w));
lseek64(state->rawdev.rawdev_fd, state->rawdev.curr_pos_w, SEEK_SET);
}
}
return 0;
}
/**
* @brief Write single JPEG frame
*
* This function will write single JPEG file
* @param state a pointer to a structure containing current state
* @return
*/
int camogm_frame_jpeg(camogm_state *state)
{
int i, j;
// int fd;
ssize_t iovlen, l;
struct iovec chunks_iovec[7];
int i, j, k, split_index;
int chunks_used = state->chunk_index - 1;
ssize_t iovlen, l = 0;
struct iovec chunks_iovec[8];
unsigned char *split_ptr = NULL;
long split_cntr = 0;
int port = state->port_num;
if (!state->rawdev_op) {
l = 0;
for (i = 0; i < (state->chunk_index) - 1; i++) {
chunks_iovec[i].iov_base = state->packetchunks[i + 1].chunk;
chunks_iovec[i].iov_len = state->packetchunks[i + 1].bytes;
l += chunks_iovec[i].iov_len;
}
sprintf(state->path, "%s%010ld_%06ld.jpeg", state->path_prefix, state->this_frame_params.timestamp_sec, state->this_frame_params.timestamp_usec);
// if ((devfd = open("/dev/fpgaio", O_RDWR))<0) {printf("error opening /dev/fpgaio\r\n"); return -1;}
//_1__12_Error opening /tmp/z/video1195147018_273452.jpeg for writing
sprintf(state->path, "%s%010ld_%06ld.jpeg", state->path_prefix, state->this_frame_params[port].timestamp_sec, state->this_frame_params[port].timestamp_usec);
if (((state->ivf = open(state->path, O_RDWR | O_CREAT, 0777))) < 0) {
D0(fprintf(debug_file, "Error opening %s for writing, returned %d, errno=%d\n", state->path, state->ivf, errno));
return -CAMOGM_FRAME_FILE_ERR;
}
iovlen = writev(state->ivf, chunks_iovec, (state->chunk_index) - 1);
if (iovlen < l) {
j = errno;
......@@ -126,10 +121,89 @@ int camogm_frame_jpeg(camogm_state *state)
return -CAMOGM_FRAME_FILE_ERR;
}
close(state->ivf);
} else {
D0(fprintf(debug_file, "\n%s: current pointers start_pos = %llu, end_pos = %llu, curr_pos = %llu, data in buffer %d\n", __func__,
state->rawdev.start_pos, state->rawdev.end_pos, state->rawdev.curr_pos_w, l));
split_index = -1;
for (int i = 0, total_len = 0; i < state->chunk_index - 1; i++) {
total_len += state->packetchunks[i + 1].bytes;
if (total_len + state->rawdev.curr_pos_w > state->rawdev.end_pos) {
split_index = i;
chunks_used++;
D0(fprintf(debug_file, "\n>>> raw storage roll over detected\n"));
break;
}
}
k = 0;
l = 0;
for (int i = 0; i < chunks_used; i++) {
++k;
if (i == split_index) {
// one of the chunks rolls over the end of the raw storage, split it into two segments and
// use additional chunk in chunks_iovec for this additional segment
split_cntr = state->rawdev.end_pos - (l + state->rawdev.curr_pos_w);
split_ptr = state->packetchunks[k].chunk + split_cntr;
D3(fprintf(debug_file, "Splitting chunk #%d: total chunk size = %ld, start address = 0x%p\n",
i, state->packetchunks[k].bytes, state->packetchunks[k].chunk));
// be careful with indexes here
chunks_iovec[i].iov_base = state->packetchunks[k].chunk;
chunks_iovec[i].iov_len = split_cntr;
l += chunks_iovec[i].iov_len;
chunks_iovec[++i].iov_base = split_ptr + 1;
chunks_iovec[i].iov_len = state->packetchunks[k].bytes - split_cntr;
l += chunks_iovec[i].iov_len;
} else {
chunks_iovec[i].iov_base = state->packetchunks[k].chunk;
chunks_iovec[i].iov_len = state->packetchunks[k].bytes;
l += chunks_iovec[i].iov_len;
}
}
if (split_index < 0) {
iovlen = writev(state->rawdev.rawdev_fd, chunks_iovec, chunks_used);
} else {
iovlen = writev(state->rawdev.rawdev_fd, chunks_iovec, split_index + 1);
fprintf(debug_file, "write first part: split_index = %d, %d bytes written\n", split_index, iovlen);
if (lseek64(state->rawdev.rawdev_fd, state->rawdev.start_pos, SEEK_SET) != state->rawdev.start_pos) {
perror(__func__);
D0(fprintf(debug_file, "error positioning file pointer to the beginning of raw device\n"));
return -CAMOGM_FRAME_FILE_ERR;
}
state->rawdev.overrun++;
iovlen += writev(state->rawdev.rawdev_fd, &chunks_iovec[split_index + 1], chunks_used - split_index);
fprintf(debug_file, "write second part: split_index + 1 = %d, chunks_used - split_index = %d, %d bytes written in total\n",
split_index + 1, chunks_used - split_index, iovlen);
}
if (iovlen < l) {
j = errno;
perror(__func__);
D0(fprintf(debug_file, "writev error %d (returned %d, expected %d)\n", j, iovlen, l));
return -CAMOGM_FRAME_FILE_ERR;
}
state->rawdev.curr_pos_w += l;
if (state->rawdev.curr_pos_w > state->rawdev.end_pos)
state->rawdev.curr_pos_w = state->rawdev.curr_pos_w - state->rawdev.end_pos + state->rawdev.start_pos;
D0(fprintf(debug_file, "%d bytes written, curr_pos = %llu\n", l, state->rawdev.curr_pos_w));
}
return 0;
}
int camogm_end_jpeg(void)
/**
* @brief Finish JPEG file write operation
*
* This function checks whether raw device write was on and closes raw device file.
* @param state a pointer to a structure containing current state
* @return 0 if the device was closed successfully and -1 otherwise
*/
int camogm_end_jpeg(camogm_state *state)
{
return 0;
int ret = 0;
if (state->rawdev_op) {
ret = close(state->rawdev.rawdev_fd);
D0(fprintf(debug_file, "Closing raw device %s\n", state->rawdev.rawdev_path));
}
return ret;
}
/** @file camogm_jpeg.h
* @brief Provides writing to series of individual JPEG files for @e camogm
* @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/>.
*/
#ifndef _CAMOGM_JPEG_H
#define _CAMOGM_JPEG_H
#include "camogm.h"
int camogm_init_jpeg(void);
int camogm_init_jpeg(camogm_state *state);
int camogm_start_jpeg(camogm_state *state);
int camogm_frame_jpeg(camogm_state *state);
int camogm_end_jpeg(void);
int camogm_end_jpeg(camogm_state *state);
void camogm_free_jpeg(void);
#endif /* _CAMOGM_JPEG_H */
/*!***************************************************************************
*! FILE NAME : camogm_kml.c
*! DESCRIPTION: Provides writing to series of individual JPEG files for camogm
*! Copyright (C) 2007 Elphel, Inc.
*! -----------------------------------------------------------------------------**
*! 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/>.
*! -----------------------------------------------------------------------------**
*!
*! $Log: camogm_kml.c,v $
*! Revision 1.3 2011/01/03 22:00:29 elphel
*! fixed tilt generation
*!
*! Revision 1.2 2009/02/25 17:50:02 spectr_rain
*! removed deprecated dependency
*!
*! Revision 1.1.1.1 2008/11/27 20:04:01 elphel
*!
*!
*! Revision 1.2 2008/04/13 21:05:20 elphel
*! Fixing KML generation
*!
*! Revision 1.1 2008/04/11 23:06:52 elphel
*! files to handle KML generation
*!
*!
/** @file camogm_kml.c
* @brief Provides writing to series of individual KML files for @e camogm
* @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/>.
*/
//!Not all are needed, just copied from the camogm.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
//#include <ctype.h>
//#include <getopt.h>
#include <time.h>
#include <string.h>
#include <netinet/in.h> /*little <-> big endian ?*/
#include <sys/mman.h> /* mmap */
#include <sys/ioctl.h>
#include <c313a.h>
#include <unistd.h>
#include <asm/byteorder.h>
#include <ogg/ogg.h> // has to be before ogmstreams.h
#include "ogmstreams.h" // move it to <>?
#include "camogm_kml.h"
const char ExifDirFileName[] = "/dev/exif_metadir";
//! may add something - called first time format is changed to this one (only once) recording is stopped
/**
* @brief Called when format is changed to MOV (only once) and recording is stopped
*/
int camogm_init_kml(void)
{
return 0;
}
void camogm_free_kml(void)
{
}
/*
int camogm_start_mov(void) {
//! allocate memory for the frame index table
if (!((state->frame_lengths=malloc(4*state->max_frames)))) return -CAMOGM_FRAME_MALLOC ;
//! open file for writing
sprintf(state->path,"%s%010ld_%06ld.mov",state->path_prefix,state->frame_params.timestamp_sec,state->frame_params.timestamp_usec);
if (((state->ivf=open(state->path,O_RDWR | O_CREAT, 0777)))<0){
D0(fprintf (debug_file, "Error opening %s for writing, returned %d, errno=%d\n", state->path,state->ivf,errno));
return -CAMOGM_FRAME_FILE_ERR;
}
//!skip header (plus extra)
//! Quicktime (and else?) - frame data start (0xff 0xd8...)
state->frame_data_start=QUICKTIME_MIN_HEADER+16+ 4*(state->max_frames)+ ( 4*(state->max_frames))/(state->frames_per_chunk); // 8 bytes for "skip" tag
lseek(state->ivf, state->frame_data_start, SEEK_SET);
return 0;
}
/**
* @brief Start KML recording
* @param[in] state a pointer to a structure containing current state
* @return 0 if recording started successfully and negative error code otherwise
*/
int camogm_start_kml(camogm_state *state)
{
// struct exif_dir_table_t kml_exif[ExifKmlNumber] ; //! store locations of the fields needed for KML generations in the Exif block
///exif_metadir
/// state->kml_exif[i]
/// Re-read directory table and rebuild state->kml_exif when starting each file
// Re-read directory table and rebuild state->kml_exif when starting each file
struct exif_dir_table_t dir_table_entry;
int fd_ExifDir;
int indx;
for (indx = 0; indx < ExifKmlNumber; indx++) state->kml_exif[indx].ltag = 0;
//! open Exif header directory file
// open Exif header directory file
fd_ExifDir = open(ExifDirFileName, O_RDONLY);
if (fd_ExifDir < 0) { // check control OK
D0(fprintf(debug_file, "Error opening %s\n", ExifDirFileName));
......@@ -139,12 +88,12 @@ int camogm_start_kml(camogm_state *state)
}
}
close(fd_ExifDir);
sprintf(state->kml_path, "%s%010ld_%06ld.kml", state->path_prefix, state->this_frame_params.timestamp_sec, state->this_frame_params.timestamp_usec);
sprintf(state->kml_path, "%s%010ld_%06ld.kml", state->path_prefix, state->this_frame_params[state->port_num].timestamp_sec, state->this_frame_params[state->port_num].timestamp_usec);
if (!((state->kml_file = fopen(state->kml_path, "w+"))) ) {
D0(fprintf(debug_file, "Error opening %s for writing\n", state->kml_path));
return -CAMOGM_FRAME_FILE_ERR;
}
/// write start of the KML file
// write start of the KML file
fprintf(state->kml_file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
"<kml xmlns=\"http://earth.google.com/kml/2.2\">\n");
fprintf(state->kml_file, "<Document>\n");
......@@ -152,6 +101,11 @@ int camogm_start_kml(camogm_state *state)
return 0;
}
/**
* @brief Write KML file for current frame
* @param[in] state a pointer to a structure containing current state
* @return 0 if file was created successfully and negative error code otherwise
*/
int camogm_frame_kml(camogm_state *state)
{
char JPEGFileName[300];
......@@ -165,17 +119,18 @@ int camogm_frame_kml(camogm_state *state)
int hours = 0, minutes = 0;
double seconds = 0.0;
int * ip;
int port = state->port_num;
if (state->kml_file) { // probably not needed
i = state->this_frame_params.timestamp_sec - (state->kml_last_ts + state->kml_period);
if ((i > 1) || ((i == 0) && ( state->this_frame_params.timestamp_usec > state->kml_last_uts ))) {
// if (state->this_frame_params.timestamp_sec > (state->kml_last_ts + state->kml_period)) { // this way it is safe to put kml_period=1000, then kml_period=1
state->kml_last_ts = state->this_frame_params.timestamp_sec;
state->kml_last_uts = state->this_frame_params.timestamp_usec;
i = state->this_frame_params[state->port_num].timestamp_sec - (state->kml_last_ts + state->kml_period);
if ((i > 1) || ((i == 0) && ( state->this_frame_params[state->port_num].timestamp_usec > state->kml_last_uts ))) {
// if (state->this_frame_params.timestamp_sec > (state->kml_last_ts + state->kml_period)) { // this way it is safe to put kml_period=1000, then kml_period=1
state->kml_last_ts = state->this_frame_params[state->port_num].timestamp_sec;
state->kml_last_uts = state->this_frame_params[state->port_num].timestamp_usec;
if (state->format == CAMOGM_FORMAT_JPEG) {
strcpy(JPEGFileName, state->path);
} else {
sprintf(JPEGFileName, "%s%010ld_%06ld.jpeg", state->path_prefix, state->this_frame_params.timestamp_sec, state->this_frame_params.timestamp_usec);
sprintf(JPEGFileName, "%s%010ld_%06ld.jpeg", state->path_prefix, state->this_frame_params[state->port_num].timestamp_sec, state->this_frame_params[state->port_num].timestamp_usec);
if (((fd_JPEG = open(JPEGFileName, O_RDWR | O_CREAT, 0777))) >= 0) {
l = 0;
for (i = 0; i < (state->chunk_index) - 1; i++) {
......@@ -197,21 +152,21 @@ int camogm_frame_kml(camogm_state *state)
}
}
/// now we have JPEGFileName written. find realtive (to KML) location:
// now we have JPEGFileName written. find realtive (to KML) location:
filename = strrchr(JPEGFileName, '/');
filename[0] = '\0';
filename++;
///generating KML itself
/// Using GPS time - in the same structure
// generating KML itself
// using GPS time - in the same structure
if (state->kml_exif[Exif_GPSInfo_GPSDateStamp_Index].ltag == Exif_GPSInfo_GPSDateStamp) { // Exif_GPSInfo_GPSDateStamp is present in template
memcpy(datestr, &(state->ed[state->kml_exif[Exif_GPSInfo_GPSDateStamp_Index].dst]), 10);
memcpy(datestr, &(state->ed[port][state->kml_exif[Exif_GPSInfo_GPSDateStamp_Index].dst]), 10);
datestr[4] = '-'; datestr[7] = '-'; datestr[10] = '\0';
}
if (state->kml_exif[Exif_GPSInfo_GPSTimeStamp_Index].ltag == Exif_GPSInfo_GPSTimeStamp) { // Exif_GPSInfo_GPSTimeStamp is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_GPSTimeStamp_Index].dst]);
hours = __cpu_to_be32( ip[0]);
minutes = __cpu_to_be32( ip[2]);
seconds = (1.0 * (__cpu_to_be32( ip[4]) + 1)) / __cpu_to_be32( ip[5]); /// GPS likes ".999", let's inc by one - anyway will round that out
seconds = (1.0 * (__cpu_to_be32( ip[4]) + 1)) / __cpu_to_be32( ip[5]); // GPS likes ".999", let's inc by one - anyway will round that out
D2(fprintf(debug_file, "(when) 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", ip[0], ip[1], ip[2], ip[3], ip[4], ip[5]));
D2(fprintf(debug_file, "(when) 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", __cpu_to_be32(ip[0]), \
__cpu_to_be32(ip[1]), \
......@@ -222,66 +177,66 @@ int camogm_frame_kml(camogm_state *state)
}
D1(fprintf(debug_file, "when=%sT%02d:%02d:%05.2fZ\n", datestr, hours, minutes, seconds));
/// knowing format provided from GPS - degrees and minuts only, no seconds:
// knowing format provided from GPS - degrees and minuts only, no seconds:
if (state->kml_exif[Exif_GPSInfo_GPSLongitude_Index].ltag == Exif_GPSInfo_GPSLongitude) { // Exif_GPSInfo_GPSLongitude is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_GPSLongitude_Index].dst]);
longitude = __cpu_to_be32( ip[0]) / (1.0 * __cpu_to_be32( ip[1])) + __cpu_to_be32( ip[2]) / (60.0 * __cpu_to_be32( ip[3]));
if ((state->kml_exif[Exif_GPSInfo_GPSLongitudeRef_Index].ltag == Exif_GPSInfo_GPSLongitudeRef) &&
(state->ed[state->kml_exif[Exif_GPSInfo_GPSLongitudeRef_Index].dst] != 'E')) longitude = -longitude;
D2(fprintf(debug_file, "(longitude) 0x%x 0x%x 0x%x 0x%x '%c'\n", ip[0], ip[1], ip[2], ip[3], state->ed[state->kml_exif[Exif_GPSInfo_GPSLongitudeRef_Index].dst]));
(state->ed[port][state->kml_exif[Exif_GPSInfo_GPSLongitudeRef_Index].dst] != 'E')) longitude = -longitude;
D2(fprintf(debug_file, "(longitude) 0x%x 0x%x 0x%x 0x%x '%c'\n", ip[0], ip[1], ip[2], ip[3], state->ed[port][state->kml_exif[Exif_GPSInfo_GPSLongitudeRef_Index].dst]));
}
if (state->kml_exif[Exif_GPSInfo_GPSLatitude_Index].ltag == Exif_GPSInfo_GPSLatitude) { // Exif_GPSInfo_GPSLatitude is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_GPSLatitude_Index].dst]);
ip = (int*)&(state->ed[port][state->kml_exif[Exif_GPSInfo_GPSLatitude_Index].dst]);
latitude = __cpu_to_be32( ip[0]) / (1.0 * __cpu_to_be32( ip[1])) + __cpu_to_be32( ip[2]) / (60.0 * __cpu_to_be32( ip[3]));
if ((state->kml_exif[Exif_GPSInfo_GPSLatitudeRef_Index].ltag == Exif_GPSInfo_GPSLatitudeRef) &&
(state->ed[state->kml_exif[Exif_GPSInfo_GPSLatitudeRef_Index].dst] != 'N')) latitude = -latitude;
D2(fprintf(debug_file, "(latitude) 0x%x 0x%x 0x%x 0x%x '%c'\n", ip[0], ip[1], ip[2], ip[3], state->ed[state->kml_exif[Exif_GPSInfo_GPSLatitudeRef_Index].dst] ? '-' : '+'));
(state->ed[port][state->kml_exif[Exif_GPSInfo_GPSLatitudeRef_Index].dst] != 'N')) latitude = -latitude;
D2(fprintf(debug_file, "(latitude) 0x%x 0x%x 0x%x 0x%x '%c'\n", ip[0], ip[1], ip[2], ip[3], state->ed[port][state->kml_exif[Exif_GPSInfo_GPSLatitudeRef_Index].dst] ? '-' : '+'));
}
/// altitude - will be modified/replaced later
// altitude - will be modified/replaced later
if (state->kml_exif[Exif_GPSInfo_GPSAltitude_Index].ltag == Exif_GPSInfo_GPSAltitude) { // Exif_GPSInfo_GPSAltitude is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_GPSAltitude_Index].dst]);
ip = (int*)&(state->ed[port][state->kml_exif[Exif_GPSInfo_GPSAltitude_Index].dst]);
altitude = (1.0 * __cpu_to_be32( ip[0])) / __cpu_to_be32( ip[1]);
if ((state->kml_exif[Exif_GPSInfo_GPSAltitudeRef_Index].ltag == Exif_GPSInfo_GPSAltitudeRef) &&
(state->ed[state->kml_exif[Exif_GPSInfo_GPSAltitudeRef_Index].dst] != '\0')) altitude = -altitude;
D2(fprintf(debug_file, "(altitude) 0x%x 0x%x '%c'\n", ip[0], ip[1], state->ed[state->kml_exif[Exif_GPSInfo_GPSAltitudeRef_Index].dst]));
(state->ed[port][state->kml_exif[Exif_GPSInfo_GPSAltitudeRef_Index].dst] != '\0')) altitude = -altitude;
D2(fprintf(debug_file, "(altitude) 0x%x 0x%x '%c'\n", ip[0], ip[1], state->ed[port][state->kml_exif[Exif_GPSInfo_GPSAltitudeRef_Index].dst]));
}
D1(fprintf(debug_file, "longitude=%f, latitude=%f, altitude=%f\n", longitude, latitude, altitude));
/// Heading - no processing of "True/Magnetic" Exif_GPSInfo_CompassDirectionRef now (always M)
// heading - no processing of "True/Magnetic" Exif_GPSInfo_CompassDirectionRef now (always M)
if (state->kml_exif[Exif_GPSInfo_CompassDirection_Index].ltag == Exif_GPSInfo_CompassDirection) { // Exif_GPSInfo_CompassDirection is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_CompassDirection_Index].dst]);
ip = (int*)&(state->ed[port][state->kml_exif[Exif_GPSInfo_CompassDirection_Index].dst]);
heading = (1.0 * __cpu_to_be32( ip[0])) / __cpu_to_be32( ip[1]);
D2(fprintf(debug_file, "(heading) 0x%x 0x%x\n", ip[0], ip[1]));
}
///Processing 'hacked' pitch and roll (made of Exif destination latitude/longitude)
// processing 'hacked' pitch and roll (made of Exif destination latitude/longitude)
if (state->kml_exif[Exif_GPSInfo_CompassRoll_Index].ltag == Exif_GPSInfo_CompassRoll) { // Exif_GPSInfo_CompassRoll is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_CompassRoll_Index].dst]);
roll = __cpu_to_be32( ip[0]) / (1.0 * __cpu_to_be32( ip[1])) + __cpu_to_be32( ip[2]) / (60.0 * __cpu_to_be32( ip[3]));
if ((state->kml_exif[Exif_GPSInfo_CompassRollRef_Index].ltag == Exif_GPSInfo_CompassRollRef) &&
(state->ed[state->kml_exif[Exif_GPSInfo_CompassRollRef_Index].dst] != EXIF_COMPASS_ROLL_ASCII[0])) roll = -roll;
D2(fprintf(debug_file, "(roll) 0x%x 0x%x '%c'\n", ip[0], ip[1], state->ed[state->kml_exif[Exif_GPSInfo_CompassRollRef_Index].dst]));
(state->ed[port][state->kml_exif[Exif_GPSInfo_CompassRollRef_Index].dst] != EXIF_COMPASS_ROLL_ASCII[0])) roll = -roll;
D2(fprintf(debug_file, "(roll) 0x%x 0x%x '%c'\n", ip[0], ip[1], state->ed[port][state->kml_exif[Exif_GPSInfo_CompassRollRef_Index].dst]));
}
if (state->kml_exif[Exif_GPSInfo_CompassPitch_Index].ltag == Exif_GPSInfo_CompassPitch) { // Exif_GPSInfo_CompassPitch is present in template
ip = (int*)&(state->ed[state->kml_exif[Exif_GPSInfo_CompassPitch_Index].dst]);
ip = (int*)&(state->ed[port][state->kml_exif[Exif_GPSInfo_CompassPitch_Index].dst]);
pitch = __cpu_to_be32( ip[0]) / (1.0 * __cpu_to_be32( ip[1])) + __cpu_to_be32( ip[2]) / (60.0 * __cpu_to_be32( ip[3]));
if ((state->kml_exif[Exif_GPSInfo_CompassPitchRef_Index].ltag == Exif_GPSInfo_CompassPitchRef) &&
(state->ed[state->kml_exif[Exif_GPSInfo_CompassPitchRef_Index].dst] != EXIF_COMPASS_PITCH_ASCII[0])) pitch = -pitch;
D2(fprintf(debug_file, "(pitch) 0x%x 0x%x '%c'\n", ip[0], ip[1], state->ed[state->kml_exif[Exif_GPSInfo_CompassPitchRef_Index].dst]));
(state->ed[port][state->kml_exif[Exif_GPSInfo_CompassPitchRef_Index].dst] != EXIF_COMPASS_PITCH_ASCII[0])) pitch = -pitch;
D2(fprintf(debug_file, "(pitch) 0x%x 0x%x '%c'\n", ip[0], ip[1], state->ed[port][state->kml_exif[Exif_GPSInfo_CompassPitchRef_Index].dst]));
}
/// convert from GPS heading, pitch, roll to KML heading, tilt, roll
// convert from GPS heading, pitch, roll to KML heading, tilt, roll
tilt = pitch + 90.0;
if (tilt < 0.0) tilt = 0;
else if (tilt > 180.0) tilt = 180.0;
D2(fprintf(debug_file, "heading=%f, roll=%f, pitch=%f, tilt=%f\n", heading, roll, pitch, tilt));
/// modify altitude
// modify altitude
altitude = (state->kml_height_mode ? altitude : 0.0) + state->kml_height;
/// write to KML
// write to KML
fprintf(state->kml_file, "<PhotoOverlay>\n" \
" <shape>rectangle</shape>\n" \
" <TimeStamp>\n" \
......@@ -316,6 +271,13 @@ int camogm_frame_kml(camogm_state *state)
}
/**
* @brief Finish KML file write operation
*
* This function writes trailing XML tags and closes KML file
* @param[in] state a pointer to a structure containing current state
* @return always 0
*/
int camogm_end_kml(camogm_state *state)
{
......
/** @file camogm_kml.h
* @brief Provides writing to series of individual KML files for @e camogm
* @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/>.
*/
#ifndef _CAMOGM_KML_H
#define _CAMOGM_KML_H
......
/*!***************************************************************************
*! FILE NAME : camogm_mov.c
*! DESCRIPTION: Provides writing to file compatible with Apple Quicktime(R) for camogm
*!TODO: Nothing yet here, will be added ASAP
*! Copyright (C) 2007 Elphel, Inc.
*! -----------------------------------------------------------------------------**
*! 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/>.
*! -----------------------------------------------------------------------------**
*!
*! $Log: camogm_mov.c,v $
*! Revision 1.2 2009/02/25 17:50:02 spectr_rain
*! removed deprecated dependency
*!
*! Revision 1.1.1.1 2008/11/27 20:04:01 elphel
*!
*!
*! Revision 1.4 2008/04/11 23:09:33 elphel
*! modified to handle kml generation
*!
*! Revision 1.3 2007/11/19 17:00:20 elphel
*! removed wrong dependency
*!
*! Revision 1.2 2007/11/19 03:23:21 elphel
*! 7.1.5.5 Added support for *.mov files in camogm.
*!
*! Revision 1.1 2007/11/16 08:49:57 elphel
*! Initial release of camogm - program to record video/image to the camera hard drive (or other storage)
*!
/** @file camogm_mov.c
* @brief Provides writing to file compatible with Apple Quicktime(R) for @e camogm
* @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/>.
*/
//!Not all are needed, just copied from the camogm.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
//#include <ctype.h>
//#include <getopt.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h> /*little <-> big endian ?*/
#include <sys/mman.h> /* mmap */
#include <sys/ioctl.h>
#include <c313a.h>
#include <asm/byteorder.h>
#include <ogg/ogg.h> // has to be before ogmstreams.h
#include "ogmstreams.h" // move it to <>?
#include <time.h>
#include <sys/types.h>
#include "camogm_mov.h"
#define QUICKTIME_MIN_HEADER 0x300 // Quicktime header length (w/o index tables) enough to accomodate
// static data .
/** @brief Quicktime header length (w/o index tables) enough to accommodate static data */
#define QUICKTIME_MIN_HEADER 0x300
//! for the parser
// for the parser
const char hexStr[] = "0123456789abcdef";
const char qtSourceFileName[] = "/etc/qt_source";
char comStr[1024];
......@@ -83,8 +42,7 @@ int samplesPerChunk = 10;
int framesize = 80000;
int timescale = 600;
int * sizes; // array of frame sizes
int iPos; //!position in the string "iFile"
//int oPos; //!position in the string "oFile"
int iPos; // position in the string "iFile"
int ofd; // output file descriptor (file opened by the caller)
int iFileLen;
char * q_template = NULL;
......@@ -93,8 +51,8 @@ const char *iFile = NULL;
int quicktime_template_parser(camogm_state *state,
const char * i_iFile, //! now - string containing header template
int i_ofd, //!output file descriptor (opened)
const char * i_iFile, // now - string containing header template
int i_ofd, // output file descriptor (opened)
int i_width, // width in pixels
int i_height,
int i_nframes,
......@@ -104,12 +62,15 @@ int quicktime_template_parser(camogm_state *state,
int i_timescale,
int * i_sizes,
int data_start // put zero if the header is written before data (no gap)
);
);
void putBigEndian(unsigned long d, int l);
int parse_special(void);
int parse(camogm_state *state, int top);
//! called first time format is changed to this one (only once) recording is stopped
//! read frame template from the file if it is not done yet
/**
* @brief Called when format is changed to MOV (only once) and recording is stopped.
* Read frame template from the file if it is not done yet.
*/
int camogm_init_mov(void)
{
FILE* qt_header;
......@@ -121,7 +82,6 @@ int camogm_init_mov(void)
}
fseek(qt_header, 0, SEEK_END);
size = ftell(qt_header);
//malloc(4*state->max_frames);
if (!((q_template = malloc(size + 1)))) {
D0(fprintf(debug_file, "Could not allocate %d bytes of memory for Quicktime header template\n", (size + 1)));
fclose(qt_header);
......@@ -147,24 +107,34 @@ void camogm_free_mov(void)
}
}
/**
* @brief Start MOV recording
* @param[in] state a pointer to a structure containing current state
* @return 0 if recording started successfully and negative error code otherwise
*/
int camogm_start_mov(camogm_state *state)
{
//! allocate memory for the frame index table
// allocate memory for the frame index table
if (!((state->frame_lengths = malloc(4 * state->max_frames)))) return -CAMOGM_FRAME_MALLOC;
//! open file for writing
sprintf(state->path, "%s%010ld_%06ld.mov", state->path_prefix, state->frame_params.timestamp_sec, state->frame_params.timestamp_usec);
// open file for writing
sprintf(state->path, "%s%010ld_%06ld.mov", state->path_prefix, state->frame_params[state->port_num].timestamp_sec, state->frame_params[state->port_num].timestamp_usec);
if (((state->ivf = open(state->path, O_RDWR | O_CREAT, 0777))) < 0) {
D0(fprintf(debug_file, "Error opening %s for writing, returned %d, errno=%d\n", state->path, state->ivf, errno));
return -CAMOGM_FRAME_FILE_ERR;
}
//!skip header (plus extra)
//! Quicktime (and else?) - frame data start (0xff 0xd8...)
// skip header (plus extra)
// Quicktime (and else?) - frame data start (0xff 0xd8...)
state->frame_data_start = QUICKTIME_MIN_HEADER + 16 + 4 * (state->max_frames) + ( 4 * (state->max_frames)) / (state->frames_per_chunk); // 8 bytes for "skip" tag
lseek(state->ivf, state->frame_data_start, SEEK_SET);
return 0;
}
/**
* @brief Write a frame to file
* @param[in] state a pointer to a structure containing current state
* @return 0 if frame was saved successfully and negative error code otherwise
*/
int camogm_frame_mov(camogm_state *state)
{
int i, j;
......@@ -197,34 +167,31 @@ int camogm_frame_mov(camogm_state *state)
*/
int camogm_end_mov(camogm_state *state)
{
off_t l/*,he;
unsigned char mdat_tag[8];
unsigned char skip_tag[]="\0\0\0\0skip"*/;
timescale = 10000; //! frame period measured in 1/10000 of a second?
//! that was in old code. If that works - try to switch to microseconds
l = lseek(state->ivf, 0, SEEK_CUR) - (state->frame_data_start) + 8; //!4-byte length+"mdat"
// lseek(state->ivf, state->frame_data_start, SEEK_SET);
// fill in the header in the beginning of the file
off_t l;
int port = state->port_num;
timescale = 10000; // frame period measured in 1/10000 of a second?
// that was in old code. If that works - try to switch to microseconds
l = lseek(state->ivf, 0, SEEK_CUR) - (state->frame_data_start) + 8; // 4-byte length+"mdat"
// fill in the header in the beginning of the file
lseek(state->ivf, 0, SEEK_SET);
quicktime_template_parser(state,
q_template, //! now - string containing header template
state->ivf, //!output file descriptor (opened)
state->width, //! width in pixels
q_template, // now - string containing header template
state->ivf, // output file descriptor (opened)
state->width, // width in pixels
state->height,
state->frameno,
state->frame_period / (1000000 / timescale),
state->frame_period[port] / (1000000 / timescale),
state->frames_per_chunk,
0, //!frame size - will look in the table
0, // frame size - will look in the table
(int)((float)timescale / (state->timescale)),
state->frame_lengths, //! array of frame lengths to build an index
state->frame_lengths, // array of frame lengths to build an index
state->frame_data_start
);
#if 0
//! now we need to overwrite last mdat tag in the header to the skip the gap, instead of the length 'mdat
//! put length 'skip length 'mdat
// now we need to overwrite last mdat tag in the header to the skip the gap, instead of the length 'mdat
// put length 'skip length 'mdat
he = lseek(state->ivf, 0, SEEK_CUR); // just after the original header end
l = state->frame_data_start - he; //! should be >=
l = state->frame_data_start - he; // should be >=
D4(fprintf(debug_file, "Remaining gap between Quicktime header and the data is %d (it should be>=8) \n", (int)l));
lseek(state->ivf, he - 8, SEEK_SET);
read(state->ivf, mdat_tag, 8); //!read calculated length+'mdat' tag
......@@ -239,7 +206,7 @@ int camogm_end_mov(camogm_state *state)
#endif
close(state->ivf);
state->ivf = -1;
//! free memory used for index
// free memory used for index
if (state->frame_lengths) {
free(state->frame_lengths);
state->frame_lengths = NULL;
......@@ -253,7 +220,7 @@ int camogm_end_mov(camogm_state *state)
* on exit - input pointer - after closing "}", output after it's output
* @param[in] d
* @param[in] l
* @return none
* @return None
*/
void putBigEndian(unsigned long d, int l)
{
......@@ -264,18 +231,16 @@ void putBigEndian(unsigned long d, int l)
od[1] = d >> 16;
od[0] = d >> 24;
if (l) write(ofd, &od[4 - l], l);
// oPos+=l;
}
//!temporary replacement for fgets to read from string
/** @brief Temporary replacement for fgets to read from string */
char * sfgets(char * str, int size, const char * stream, int * pos)
{
int l;
const char * eol = strchr(&stream[*pos], '\n');
if (!eol) eol = stream + (strlen(stream) - 1); //!pointer to last before \0
if (!eol) eol = stream + (strlen(stream) - 1); // pointer to last before '\0'
l = (eol - stream) - (*pos);
// if (l >= size) eol=stream+ (*pos+size-1);
if (l >= size) l = size - 1;
memcpy(str, &stream[*pos], l);
str[l] = '\0';
......@@ -285,7 +250,6 @@ char * sfgets(char * str, int size, const char * stream, int * pos)
int parse_special(void)
{
time_t ltime;
int n, j, l;
char str[256];
......@@ -293,7 +257,6 @@ int parse_special(void)
int i = 0;
int gap;
// while (((c=fgetc(infile))!=0x20) && (c!=0x09) && (c!=0x0a) && (c!=0x0d) && (c!=0x0) && (i<255) && ( feof(infile) == 0 )) str[i++]=c;
while (((c = iFile[iPos++]) != 0x20) && (c != 0x09) && (c != 0x0a) && (c != 0x0d) && (c != 0x0) && (i < 255) && ( iPos < iFileLen )) str[i++] = c;
str[i] = 0;
......@@ -345,10 +308,10 @@ int parse_special(void)
} else for (i = 0; i < n; i++) putBigEndian(headerSize + framesize * samplesPerChunk * i, 4);
return 0;
}
//! a hack - invlude length'skip if data position (header size is known and there is a gap)
// a hack - invlude length'skip if data position (header size is known and there is a gap)
if (strcmp(str, "data_size") == 0) {
gap = headerSize - lseek(ofd, 0, SEEK_CUR) - 8;
if (gap > 0) { //!it should be exactly 0 if there is no gap or >8 if there is
if (gap > 0) { //it should be exactly 0 if there is no gap or >8 if there is
D4(fprintf(debug_file, "Inserting a skip tag to compensate for a gap (%d bytes) between the header and the frame data\n", gap));
if (gap < 8) {
D0(fprintf(debug_file, "not enough room to insret 'skip' tag - %d (need 8)\n", gap));
......@@ -358,7 +321,7 @@ int parse_special(void)
putBigEndian(gap, 4);
D4(fprintf(debug_file, "writing string <%s>\n", "skip"));
write(ofd, "skip", 4);
lseek(ofd, gap - 8, SEEK_CUR); //! lseek over the gap and proceed as before
lseek(ofd, gap - 8, SEEK_CUR); // lseek over the gap and proceed as before
}
if (sizes != NULL) {
l = 0;
......@@ -384,30 +347,23 @@ int parse(camogm_state *state, int top) // if top - will not include length
char * cp;
D4(fprintf(debug_file, "parse(%x)\n", top));
// c=fgetc(infile);
c = iFile[iPos++];
D5(fprintf(debug_file, "%c", c));
// out_start=ftell (outfile);
// out_start=oPos;
// out_start=oPos=lseek(ofd,0,SEEK_CUR);
out_start = lseek(ofd, 0, SEEK_CUR);
if (!top) putBigEndian(0, 4);
// while (( feof(infile) == 0 ) && (c!='}')) {
while (( iPos < iFileLen ) && (c != '}')) {
// skip white spaces strchr
// skip white spaces strchr
if ((c != ' ') && (c != 0x9) && (c != 0xa) && (c != 0xd)) {
if (c == '!') {
if (parse_special() < 0) return -1;
}
// children atoms
// children atoms
else if (c == '{') {
if (parse(state, 0) < 0) return -1;
// skip comments
// } else if (c=='#') fgets( comStr, sizeof(comStr), infile);
// skip comments
} else if (c == '#') sfgets( comStr, sizeof(comStr), iFile, &iPos);
else if (c == '\'') {
// fgets ( comStr, sizeof(comStr), infile);
sfgets( comStr, sizeof(comStr), iFile, &iPos);
if ((cp = strchr(comStr, 0x0a)) != NULL) cp[0] = 0;
if ((cp = strchr(comStr, 0x0d)) != NULL) cp[0] = 0;
......@@ -415,8 +371,6 @@ int parse(camogm_state *state, int top) // if top - will not include length
cp = comStr + strlen(comStr) - 1;
while ((cp > comStr) && ((cp[0] == 0x20) || (cp[0] == 0x09))) cp--;
cp[1] = 0;
// fwrite (comStr,1, strlen(comStr),outfile);
// memcpy(&oFile[oPos],comStr,strlen(comStr));
write(ofd, comStr, strlen(comStr));
D4(fprintf(debug_file, "writing string <%s>\n", comStr));
} else if (strchr(hexStr, c)) {
......@@ -440,22 +394,15 @@ int parse(camogm_state *state, int top) // if top - will not include length
}
}
// c=fgetc(infile);
c = iFile[iPos++];
}
// fread fseek ftell
if (!top) {
// out_end=ftell (outfile);
// out_end=oPos;
out_end = lseek(ofd, 0, SEEK_CUR);
// fseek (outfile,out_start,SEEK_SET);
// oPos=out_start;
lseek(ofd, out_start, SEEK_SET);
putBigEndian((out_end - out_start), 4);
// fseek (outfile,out_end,SEEK_SET);
// oPos=out_end;
lseek(state->ivf, out_end, SEEK_SET);
}
return 0;
......@@ -463,8 +410,8 @@ int parse(camogm_state *state, int top) // if top - will not include length
int quicktime_template_parser( camogm_state *state,
const char * i_iFile, //! now - string containing header template
int i_ofd, //!output file descriptor (opened)
const char * i_iFile, // now - string containing header template
int i_ofd, // output file descriptor (opened)
int i_width, // width in pixels
int i_height,
int i_nframes,
......@@ -474,7 +421,7 @@ int quicktime_template_parser( camogm_state *state,
int i_timescale,
int * i_sizes,
int data_start // zero if dfata is not written yet (will be no gap)
)
)
{
iFile = i_iFile;
width = i_width;
......@@ -485,31 +432,20 @@ int quicktime_template_parser( camogm_state *state,
framesize = i_framesize;
timescale = i_timescale;
sizes = i_sizes;
iPos = 0; //!position in the string "iFile"
iPos = 0; // position in the string "iFile"
ofd = i_ofd;
iFileLen = strlen(iFile);
lseek(ofd, 0, SEEK_SET);
if (data_start) headerSize = data_start;
// int iFileLen=strlen(inFIle);
D3(fprintf(debug_file, "PASS I\n"));
// while ( feof(infile) == 0 ) parse(1); // pass 1
while ( iPos < iFileLen ) parse(state, 1); // pass 1
// headerSize=ftell (outfile);
// fseek (outfile,0,SEEK_SET); // rewind for pass 2
// fseek (infile, 0,SEEK_SET); //
// headerSize=oPos;
if (!headerSize) headerSize = lseek(ofd, 0, SEEK_CUR);
iPos = 0;
// oPos=0;
lseek(ofd, 0, SEEK_SET);
D3(fprintf(debug_file, "PASS II\n"));
// while ( feof(infile) == 0 ) parse(1); // pass 2
while ( iPos < iFileLen ) parse(state, 1); // pass 2
//fclose (infile);
//fclose (outfile);
// oFile[oPos]='\0';
return 0;
}
/** @file camogm_mov.h
* @brief Provides writing to file compatible with Apple Quicktime(R) for @e camogm
* @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/>.
*/
#ifndef _CAMOGM_MOV_H
#define _CAMOG_MOV_H
......
/*!***************************************************************************
*! FILE NAME : camogm_ogm.c
*! DESCRIPTION: Provides writing to ogm files for camogm
*! Copyright (C) 2007 Elphel, Inc.
*! -----------------------------------------------------------------------------**
*! 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/>.
*! -----------------------------------------------------------------------------**
*!
*! $Log: camogm_ogm.c,v $
*! Revision 1.2 2009/02/25 17:50:02 spectr_rain
*! removed deprecated dependency
*!
*! Revision 1.1.1.1 2008/11/27 20:04:01 elphel
*!
*!
*! Revision 1.2 2007/11/19 03:23:21 elphel
*! 7.1.5.5 Added support for *.mov files in camogm.
*!
*! Revision 1.1 2007/11/16 08:49:58 elphel
*! Initial release of camogm - program to record video/image to the camera hard drive (or other storage)
*!
/** @file camogm_ogm.c
* @brief Provides writing to OGM files for @e camogm
* @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/>.
*/
//!Not all are needed, just copied from the camogm.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
//#include <ctype.h>
//#include <getopt.h>
#include <time.h>
#include <string.h>
#include <netinet/in.h> /*little <-> big endian ?*/
#include <sys/mman.h> /* mmap */
#include <sys/ioctl.h>
#include <c313a.h>
#include <asm/byteorder.h>
#include <ogg/ogg.h> // has to be before ogmstreams.h
#include "ogmstreams.h" // move it to <>?
#include "camogm_ogm.h"
//! may add something - called first time format is changed to this one (only once) recording is stopped
/**
* @brief Called when format is changed to OGM (only once) and recording is stopped
*/
int camogm_init_ogm(void)
{
return 0;
}
void camogm_free_ogm(void)
{
}
/**
* @brief Start OGM recording
* @param[in] state a pointer to a structure containing current state
* @return 0 if recording started successfully and negative error code otherwise
*/
int camogm_start_ogm(camogm_state *state)
{
char vendor[] = "ElphelOgm v 0.1";
......@@ -76,7 +49,7 @@ int camogm_start_ogm(camogm_state *state)
char hdbuf[sizeof(sh) + 1];
ogg_packet ogg_header;
sprintf(state->path, "%s%010ld_%06ld.ogm", state->path_prefix, state->frame_params.timestamp_sec, state->frame_params.timestamp_usec);
sprintf(state->path, "%s%010ld_%06ld.ogm", state->path_prefix, state->frame_params[state->port_num].timestamp_sec, state->frame_params[state->port_num].timestamp_usec);
if (!((state->vf = fopen(state->path, "w+"))) ) {
D0(fprintf(debug_file, "Error opening %s for writing\n", state->path));
return -CAMOGM_FRAME_FILE_ERR;
......@@ -87,20 +60,17 @@ int camogm_start_ogm(camogm_state *state)
memcpy(sh.streamtype, "video", 5);
memcpy(sh.subtype, "MJPG", 4);
put_uint32(&sh.size, sizeof(sh));
// put_uint64(&sh.time_unit, (ogg_int64_t) 10*state->frame_period);
put_uint64(&sh.time_unit, state->time_unit);
// put_uint64(&sh.samples_per_unit, 1);
put_uint64(&sh.samples_per_unit, (ogg_int64_t)state->timescale);
put_uint32(&sh.default_len, 1);
put_uint32(&sh.buffersize, state->width * state->height);
// put_uint16(&sh.bits_per_sample, 24); //?
put_uint16(&sh.bits_per_sample, 0); //?
put_uint16(&sh.bits_per_sample, 0);
put_uint32(&sh.sh.video.width, state->width);
put_uint32(&sh.sh.video.height, state->height);
memcpy(&hdbuf[1], &sh, sizeof(sh));
hdbuf[0] = 1;
//! put it into Ogg stream
ogg_header.packet = hdbuf;
// put it into Ogg stream
ogg_header.packet = (unsigned char *)hdbuf;
ogg_header.bytes = sizeof(sh) + 1;
ogg_header.b_o_s = 1;
ogg_header.e_o_s = 0;
......@@ -108,19 +78,18 @@ int camogm_start_ogm(camogm_state *state)
ogg_header.granulepos = 0;
ogg_stream_packetin(&(state->os), &ogg_header);
// while(ogg_stream_pageout(&(state->os), &(state->og))) {
// while(ogg_stream_pageout(&(state->os), &(state->og))) {
while (ogg_stream_flush(&(state->os), &(state->og))) {
int i, j;
int i;
if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
(state->og.body_len && (((i = fwrite(state->og.body, 1, state->og.body_len, state->vf))) != state->og.body_len))) {
j = errno;
D2(fprintf(debug_file, "\n%d %ld %ld\n", i, state->og.header_len, state->og.body_len));
return -CAMOGM_FRAME_FILE_ERR;
}
}
//!create comment
//!use fixed minimal one - hdbuf will be enough for that
// create comment
// use fixed minimal one - hdbuf will be enough for that
memset(hdbuf, 0, sizeof(hdbuf));
hdbuf[0] = PACKET_TYPE_COMMENT;
memcpy(&hdbuf[1], "vorbis", 6);
......@@ -132,43 +101,33 @@ int camogm_start_ogm(camogm_state *state)
put_uint32(&hdbuf[pos], 0);
pos += 4;
hdbuf[pos++] = 1;
//! put it into Ogg stream
ogg_header.packet = hdbuf;
// put it into Ogg stream
ogg_header.packet = (unsigned char *)hdbuf;
ogg_header.bytes = pos;
ogg_header.b_o_s = 0;
ogg_header.e_o_s = 0;
ogg_header.packetno = state->packetno++;;
ogg_header.granulepos = 0;
ogg_stream_packetin(&(state->os), &ogg_header);
/*
while(ogg_stream_pageout(&(state->os), &(state->og))) {
int i, j;
if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
(state->og.body_len && (((i = fwrite(state->og.body, 1, state->og.body_len, state->vf))) != state->og.body_len))) {
j=errno;
D(fprintf(debug_file,"\n%d %ld %ld\n",i,state->og.header_len,state->og.body_len));
return -CAMOGM_FRAME_FILE_ERR;
}
}
*/
//! calculate initial absolute granulepos (from 1970), then increment with each frame. Later try calculating granulepos of each frame
//! from the absolute time (actual timestamp)
state->granulepos = (ogg_int64_t)( (((double)state->frame_params.timestamp_usec) +
(((double)1000000) * ((double)state->frame_params.timestamp_sec))) *
// calculate initial absolute granulepos (from 1970), then increment with each frame. Later try calculating granulepos of each frame
// from the absolute time (actual timestamp)
state->granulepos = (ogg_int64_t)( (((double)state->frame_params[state->port_num].timestamp_usec) +
(((double)1000000) * ((double)state->frame_params[state->port_num].timestamp_sec))) *
((double)10) /
((double)state->time_unit) *
((double)state->timescale));
// state->frame_period=(state->this_frame_params.timestamp_usec - state->frame_params.timestamp_usec)+
// 1000000*(state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec);
// put_uint64(&sh.time_unit, (ogg_int64_t)((double)10000000.0 / (double)fps));
//!Temporarily setting granulepos to 0 (suspect they do not process correctly 64 bits)
// temporarily setting granulepos to 0 (suspect they do not process correctly 64 bits)
state->granulepos = 0;
//! Here - Ogg stream started, both header and comment packets are sent out, next should be just data packets
// Here - Ogg stream started, both header and comment packets are sent out, next should be just data packets
return 0;
}
/**
* @brief Write a frame to file
* @param[in] state a pointer to a structure containing current state
* @return 0 if frame was saved successfully and negative error code otherwise
*/
int camogm_frame_ogm(camogm_state *state)
{
int indx;
......@@ -177,8 +136,8 @@ int camogm_frame_ogm(camogm_state *state)
elp_packet.bytes = 0;
for (indx = 0; indx < state->chunk_index; indx++) elp_packet.bytes += state->packetchunks[indx].bytes;
elp_packet.packet = state->packetchunks;
//D(fprintf (debug_file,"elp_packet.bytes=0x%lx: elp_packet.packet=%p\n",elp_packet.bytes, elp_packet.packet));
/*D(fprintf (debug_file,"0:0x%lx: %p\n" \
//D(fprintf (debug_file,"elp_packet.bytes=0x%lx: elp_packet.packet=%p\n",elp_packet.bytes, elp_packet.packet));
/*D(fprintf (debug_file,"0:0x%lx: %p\n" \
"1:0x%lx: %p\n" \
"2:0x%lx: %p\n" \
"3:0x%lx: %p\n" \
......@@ -196,15 +155,13 @@ int camogm_frame_ogm(camogm_state *state)
elp_packet.b_o_s = 0;
elp_packet.e_o_s = 0;
elp_packet.packetno = state->packetno++;;
// tts[0]=state->frame_params.timestamp_usec;
// tts[1]=state->frame_params.timestamp_sec;
elp_packet.granulepos = state->granulepos;
//!TODO: If that works, calculate granulepos from timestamp for each frame
/// @todo If that works, calculate granulepos from timestamp for each frame
state->granulepos += (ogg_int64_t)state->timescale;
//D3(fprintf (debug_file,"_121_"));
//D3(fprintf (debug_file,"_121_"));
ogg_stream_packetin_elph(&(state->os), &elp_packet);
//D3(fprintf (debug_file,"_13_"));
//D3(fprintf (debug_file,"_13_"));
while (ogg_stream_pageout(&(state->os), &(state->og))) {
int i, j;
if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
......@@ -217,11 +174,15 @@ int camogm_frame_ogm(camogm_state *state)
return 0;
}
//!Zero packets are OK, use them to end file with "last" turned on
/**
* @brief Finish OGM file operation
* @param[in] state a pointer to a structure containing current state
* @return 0 if file was saved successfully and negative error code otherwise
* @note Zero packets are OK, use them to end file with "last" turned on
*/
int camogm_end_ogm(camogm_state *state)
{
//! put zero-packet it into stream
// put zero-packet it into stream
ogg_packet ogg_header;
ogg_header.packet = NULL;
......@@ -230,12 +191,11 @@ int camogm_end_ogm(camogm_state *state)
ogg_header.e_o_s = 1;
ogg_header.packetno = state->packetno++;
ogg_header.granulepos = ++(state->granulepos);
ogg_stream_packetin(&(state->os), &ogg_header); //!+++++++++++++++++++++++++++++++++++++++++++++++++++++
ogg_stream_packetin(&(state->os), &ogg_header); // +++++++++++++++++++++++++++++++++++++++++++++++++++++
while (ogg_stream_flush(&(state->os), &(state->og))) {
int i, j;
int i;
if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
(state->og.body_len && (((i = fwrite(state->og.body, 1, state->og.body_len, state->vf))) != state->og.body_len))) {
j = errno;
D0(fprintf(debug_file, "\n%d %ld %ld\n", i, state->og.header_len, state->og.body_len));
return -CAMOGM_FRAME_FILE_ERR;
}
......
/** @file camogm_ogm.h
* @brief Provides writing to OGM files for @e camogm
* @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/>.
*/
#ifndef _CAMOGM_OGM_H
#define _CAMOGM_OGM_H
......
/** @file camogm_read.c
* @brief Provides reading data written to raw device storage and transmitting the data over a socket.
* @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/>.
*/
/**
* @addtogroup SPECIAL_INCLUDES Special includes
* These defines are needed to use lseek64, strptime and usleep and should be set before includes
* @{
*/
/** Needed for lseek64 */
#define _LARGEFILE64_SOURCE
/** Needed for strptime and usleep */
#define _XOPEN_SOURCE
/** Needed for usleep */
#define _XOPEN_SOURCE_EXTENDED
/** @} */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/limits.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <math.h>
#include <asm/byteorder.h>
#include <sys/statvfs.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include "camogm_read.h"
#include "index_list.h"
/** @brief Offset in Exif where TIFF header starts */
#define TIFF_HDR_OFFSET 12
/** @brief The date and time format of Exif field */
#define EXIF_DATE_TIME_FORMAT "%Y:%m:%d %H:%M:%S"
/** @brief The date and time format of 'find_file' command */
#define EXIF_TIMESTAMP_FORMAT "%04d:%02d:%02d_%02d:%02d:%02d"
/** @brief The format string used for file parameters reporting. Time and port number are extracted from Exif */
#define INDEX_FORMAT_STR "port_number=%d;unix_time=%ld;usec_time=%06u;offset=0x%010llx;file_size=%u\n"
/** @brief The delimiters used to separate several commands in one command string sent over socket */
#define CMD_DELIMITER "/?"
/** @brief The length of a buffer for command string */
#define CMD_BUFF_LEN 1024
/** @brief The length of a buffer for string formatting */
#define SMALL_BUFF_LEN 32
/** @brief 64 bit mask to align offsets to 4 kb page boundary */
#define PAGE_BOUNDARY_MASK 0xffffffffffffe000
/** @brief The size of read buffer in bytes. The data will be read from disk in blocks of this size */
#define PHY_BLK_SZ 4096
/** @brief Include or exclude file start and stop markers from resulting file. This must be set to 1 for JPEG files */
#define INCLUDE_MARKERS 1
/** @brief The size of file search window. This window is memory mapped. */
#define SEARCH_SIZE_WINDOW ((uint64_t)4 * (uint64_t)1048576)
/** @brief Time window (in seconds) used for disk index search. Index within this window is considered a candidate */
#define SEARCH_TIME_WINDOW 60
/** @brief File starting marker on a raw device. It corresponds to SOI JPEG marker */
static unsigned char elphelst[] = {0xff, 0xd8};
/** @brief File ending marker on a raw device. It corresponds to EOI JPEG marker */
static unsigned char elphelen[] = {0xff, 0xd9};
static const struct iovec elphel_st = {
.iov_base = elphelst,
.iov_len = sizeof(elphelst)
};
static const struct iovec elphel_en = {
.iov_base = elphelen,
.iov_len = sizeof(elphelen)
};
/** @brief X Macro for commands. Add new commands to @e COMMAND_TABLE
* @{
*/
#define COMMAND_TABLE \
X(CMD_BUILD_INDEX, "build_index") \
X(CMD_GET_INDEX, "get_index") \
X(CMD_READ_DISK, "read_disk") \
X(CMD_READ_FILE, "read_file") \
X(CMD_FIND_FILE, "find_file") \
X(CMD_NEXT_FILE, "next_file") \
X(CMD_PREV_FILE, "prev_file") \
X(CMD_READ_ALL_FILES, "read_all_files") \
X(CMD_STATUS, "status")
/** @enum socket_commands */
#define X(a, b) a,
enum socket_commands {
COMMAND_TABLE
};
#undef X
#define X(a, b) b,
static const char *cmd_list[] = {
COMMAND_TABLE
};
#undef X
/** @} */
/**
* @enum match_result
* @brief The result codes for file markers search function
* @var match_result::MATCH_FOUND
* File marker was found in the current data buffer
* @var match_result::MATCH_NOT_FOUND
* File marker was not found in the current data buffer
* @var match_result::MATCH_PARTIAL
* Only a fraction of a marker was found in the end of the current data buffer
*/
enum match_result {
MATCH_FOUND = -1,
MATCH_NOT_FOUND = -2,
MATCH_PARTIAL = -3
};
/**
* @enum search_state
* @brief The state of the program during file search in raw device buffer
* @var search_state::SEARCH_SKIP
* Skip data in read buffer
* @var search_state::SEARCH_FILE_DATA
* The data in read buffer is a file data with known start offset
*/
enum search_state {
SEARCH_SKIP,
SEARCH_FILE_DATA
};
/**
* @brief Exif data format table.
*
* This table is used to convert Exif data format to its byte length and then calculate the length
* of a record in Image File Directory. The data format value 0 does not exist thus the first
* element of the array is 0 too which gives zero length field. Exif data format can one of the
* following:
* Value | Format | Bytes/Components
* ------|-------------------|-----------------
* 1 | unsigned byte | 1
* 2 | ascii string | 1
* 3 | unsigned short | 2
* 4 | unsigned long | 4
* 5 | unsigned rational | 8
* 6 | signed byte | 1
* 7 | undefined | 1
* 8 | signed short | 2
* 9 | signed long | 4
* 10 | signed rational | 8
* 11 | signed float | 4
* 12 | double float | 8
*/
const unsigned char exif_data_fmt[] = {
0, 1, 1, 2, 4, 8, 1,
1, 2, 4, 8, 4, 8
};
/**
* @struct ifd_entry
* @brief Represents a single Image File Directory record
* @var ifd_entry::tag
* Exif tag number
* @var ifd_entry::format
* Data format in the record
* @var ifd_entry::len
* The number of components in the record
* @var ifd_entry::offset
* The data value or offset to the data
*/
struct ifd_entry {
unsigned short tag;
unsigned short format;
unsigned long len;
unsigned long offset;
};
/**
* @struct tiff_hdr
* @brief TIFF header structure
* @var tiff_hdr::byte_order
* Motorola (MM) or Inter (II) byte order
* @var tiff_hdr::mark
* Tag mark
* @var tiff_hdr::offset
* Offset to first IFD
*/
struct tiff_hdr {
unsigned short byte_order;
unsigned short mark;
unsigned long offset;
};
/**
* @struct crb_ptrs
* @brief A set of vectors pointing to a file marker crossing read buffer boundary.
* @var crb_ptrs::first_buff
* Points to the end of first read buffer where a file marker starts
* @var crb_ptrs::second_buff
* Points to the start of second read buffer where a file marker ends
*/
struct crb_ptrs {
struct iovec first_buff;
struct iovec second_buff;
};
/**
* @struct exit_state
* @brief Container for the resources which should be freed before exit
* @var exit_state::state
* Pointer to #camogm_state structure containing current program state. This structure holds
* file descriptors and memory mapped regions which should be closed and unmapped accordingly
* @var exit_state::idir
* Pointer to disk index directory. The nodes of this directory are dynamically allocated and must be freed
* @var exit_state::sockfd_const
* Socket descriptor
* @var exit_state::sockfd_temp
* Socket descriptor
*/
struct exit_state {
camogm_state *state;
struct disk_idir *idir;
struct disk_idir *sparse_idir;
int *sockfd_const;
int *sockfd_temp;
};
static inline void exit_thread(void *arg);
static void build_index(camogm_state *state, struct disk_idir *idir);
static int mmap_disk(rawdev_buffer *rawdev, const struct range *range);
static int munmap_disk(rawdev_buffer *rawdev);
/**
* @brief Debug function, prints the content of disk index directory
* @param[in] idir pointer to disk index directory to be printed
* @return None
*/
void dump_index_dir(const struct disk_idir *idir)
{
struct disk_index *ind = idir->head;
while (ind != NULL) {
fprintf(debug_file, INDEX_FORMAT_STR,
ind->port, ind->rawtime, ind->usec, ind->f_offset, ind->f_size);
fprintf(debug_file, "\tCurrent pointer: 0x%p, Prev pointer: 0x%p, next pointer: 0x%p\n", ind, ind->prev, ind->next);
ind = ind->next;
}
}
/**
* @brief Find pattern in a data buffer
*
* This function searches for the first occurrence of pattern in a data buffer and returns a pointer to
* the position of this pattern in the buffer.
* @param[in] buff_ptr pointer to an array of char values where the pattern should be found
* @param[in] buff_sz size of the data array
* @param[in] pattern pointer to an array of char values containing pattern
* @param[in] pt_sz size of the pattern array
* @param[in] add_pattern include or exclude marker from resulting buffer offset
* @return The index in data buffer where pattern matches or error code from #match_result if it was not found
*/
static int find_marker(const unsigned char * restrict buff_ptr, ssize_t buff_sz, const unsigned char * restrict pattern, ssize_t pt_sz,
int add_pattern)
{
int ret = MATCH_NOT_FOUND;
int j = 0;
int i;
for (i = 0; i < buff_sz; i++) {
if (buff_ptr[i] != pattern[j]) {
// current symbol in data buffer and first symbol of pattern does not match
j = 0;
} else if (buff_ptr[i] == pattern[j] && j < pt_sz - 1) {
// pattern symbol match
j++;
} else if (buff_ptr[i] == pattern[j] && j == pt_sz - 1) {
// last pattern symbol match
if (add_pattern)
ret = i;
else
ret = i - j;
j = 0;
break;
}
}
if (j > 0) {
// partial match found in the end of data buffer, we need more data for further comparison
ret = MATCH_PARTIAL;
}
return ret;
}
/**
* @brief Find pattern in a data buffer in reverse order
*
* This function searches for the first occurrence of pattern in a data buffer and returns a pointer to
* the position of this pattern in the buffer.
* @param[in] buff_ptr pointer to an array of char values where the pattern should be found
* @param[in] buff_sz size of the data array
* @param[in] pattern pointer to an array of char values containing pattern
* @param[in] pt_sz size of the pattern array
* @param[in] add_pattern include or exclude marker from resulting buffer offset
* @return The index in data buffer where pattern matches or error code from #match_result if it was not found
*/
static int find_marker_backward(const unsigned char * restrict buff_ptr, ssize_t buff_sz, const unsigned char * restrict pattern, ssize_t pt_sz,
int add_pattern)
{
int ret = MATCH_NOT_FOUND;
int j = 0;
for (int i = buff_sz - 1; i > 0; i--) {
if (buff_ptr[i] != pattern[j]) {
// current symbol in data buffer and last symbol of pattern does not match
j = pt_sz - 1;
} else if (buff_ptr[i] == pattern[j] && j > 0) {
// pattern symbol match
j--;
} else if (buff_ptr[i] == pattern[j] && j == 0) {
// first pattern symbol match
if (add_pattern)
ret = i - j;
else
ret = i;
j = 0;
break;
}
}
if (j > 0) {
// partial match found in the end of data buffer, we need more data for further comparison
ret = MATCH_PARTIAL;
}
return ret;
}
/**
* @brief Converts byte order for all fields in #ifd_entry structure
* @param[in,out] ifd a pointer to a structure which should be converted
* @return None
*/
static void ifd_byte_order(struct ifd_entry *ifd)
{
ifd->tag = __be16_to_cpu(ifd->tag);
ifd->format = __be16_to_cpu(ifd->format);
ifd->len = __be32_to_cpu(ifd->len);
ifd->offset = __be32_to_cpu(ifd->offset);
if (exif_data_fmt[ifd->format] == 2) {
ifd->offset = (ifd->offset >> 16) & 0xffff;
}
}
/**
* @brief Convert byte order for all fields in #tiff_hdr structure
* @param[in,out] hdr a pointer to a structure which should be converted
* @return None
*/
static void hdr_byte_order(struct tiff_hdr *hdr)
{
hdr->byte_order = __be16_to_cpu(hdr->byte_order);
hdr->mark = __be16_to_cpu(hdr->mark);
hdr->offset = __be32_to_cpu(hdr->offset);
}
/**
* @brief Read a string from Exif for a given record.
* @param[in,out] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[in] tag Exif image file directory record containing string offset
* @param[out] buff buffer for the string to be read
* @return The number of bytes placed to the read buffer
*/
static size_t exif_get_text(rawdev_buffer *rawdev, struct ifd_entry *tag, char *buff)
{
size_t bytes = 0;
size_t str_len = tag->len * exif_data_fmt[tag->format];
uint64_t curr_pos = rawdev->file_start + TIFF_HDR_OFFSET + tag->offset;
lseek64(rawdev->rawdev_fd, curr_pos, SEEK_SET);
bytes = read(rawdev->rawdev_fd, buff, str_len);
return bytes;
}
/**
* @brief Read Exif data from the file starting from #rawdev_buffer::file_start offset and
* create a new node corresponding to the offset
* @param[in,out] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[out] indx pointer to new disk index node
* @return 0 if new node was successfully created and -1 otherwise
*/
static int read_index(rawdev_buffer *rawdev, struct disk_index **indx)
{
int ret = 0;
int process = 2;
uint32_t data32;
uint16_t num_entries = 0;
uint64_t curr_pos;
uint64_t subifd_offset = 0;
struct tiff_hdr hdr;
struct ifd_entry ifd;
struct ifd_entry ifd_page_num = {0};
struct ifd_entry ifd_date_time = {0};
struct ifd_entry ifd_subsec = {0};
struct disk_index *node = NULL;
unsigned char read_buff[TIFF_HDR_OFFSET] = {0};
char str_buff[SMALL_BUFF_LEN] = {0};
uint64_t save_pos = lseek64(rawdev->rawdev_fd, 0, SEEK_CUR);
if (indx == NULL)
return -1;
curr_pos = rawdev->file_start;
lseek64(rawdev->rawdev_fd, curr_pos, SEEK_SET);
if (read(rawdev->rawdev_fd, read_buff, sizeof(read_buff)) <= 0) {
lseek64(rawdev->rawdev_fd, save_pos, SEEK_SET);
return -1;
}
if (read_buff[2] == 0xff && read_buff[3] == 0xe1) {
// get IFD0 offset from TIFF header
read(rawdev->rawdev_fd, &hdr, sizeof(struct tiff_hdr));
hdr_byte_order(&hdr);
curr_pos = rawdev->file_start + TIFF_HDR_OFFSET + hdr.offset;
lseek64(rawdev->rawdev_fd, curr_pos, SEEK_SET);
// process IFD0 and SubIFD fields
do {
read(rawdev->rawdev_fd, &num_entries, sizeof(num_entries));
num_entries = __be16_to_cpu(num_entries);
for (int i = 0; i < num_entries; i++) {
read(rawdev->rawdev_fd, &ifd, sizeof(struct ifd_entry));
ifd_byte_order(&ifd);
switch (ifd.tag) {
case Exif_Image_PageNumber:
ifd_page_num = ifd;
break;
case Exif_Photo_DateTimeOriginal & 0xffff:
ifd_date_time = ifd;
break;
case Exif_Image_ExifTag:
subifd_offset = ifd.offset;
break;
case Exif_Photo_SubSecTimeOriginal & 0xffff:
ifd_subsec = ifd;
break;
}
}
// ensure that IFD0 finished correctly (0x00000000 in the end), set file pointer to SubIFD and
// process remaining fields
read(rawdev->rawdev_fd, &data32, sizeof(data32));
process -= (subifd_offset == 0 || data32 != 0) ? 2 : 1;
curr_pos = rawdev->file_start + TIFF_HDR_OFFSET + subifd_offset;
lseek64(rawdev->rawdev_fd, curr_pos, SEEK_SET);
} while (process > 0);
// create and fill new disk index node with Exif data
if (create_node(&node) == 0) {
node->f_offset = rawdev->file_start;
if (ifd_page_num.len != 0) {
node->port = (uint32_t)ifd_page_num.offset;
}
if (ifd_date_time.len != 0) {
struct tm tm = {0};
exif_get_text(rawdev, &ifd_date_time, str_buff);
strptime(str_buff, EXIF_DATE_TIME_FORMAT, &tm);
node->rawtime = mktime(&tm);
}
if (ifd_subsec.len != 0) {
exif_get_text(rawdev, &ifd_subsec, str_buff);
node->usec = strtoul(str_buff, NULL, 10);
}
if (node->rawtime != -1) {
*indx = node;
} else {
free(node);
ret = -1;
}
} else {
ret = -1;
}
} else {
ret = -1;
}
lseek64(rawdev->rawdev_fd, save_pos, SEEK_SET);
return ret;
}
/**
* @brief Calculate the size of current file and update the value in disk index directory
* @param[in,out] indx pointer to disk index node which size should be calculated
* @param[in] pos_stop the offset of the last byte of the current file
* @return 0 if the file size was successfully updated and -1 otherwise
* @note @e pos_stop points to the last byte of the file marker thus the size is incremented
* by 1
*/
static int stop_index(struct disk_index *indx, uint64_t pos_stop)
{
int ret = 0;
if (indx != NULL) {
indx->f_size = pos_stop - indx->f_offset + 1;
} else {
ret = -1;
}
return ret;
}
/**
* @brief Detect cases when file marker crosses read buffer boundary
* @param[in] from pointer to current read buffer which can hold the beginning of
* file marker
* @param[in] to pointer to a buffer containing next chunk of data which can hold
* the end of file marker
* @param[in] marker pointer to a buffer holding file marker to be detect
* @param[in] crbp pointer to a structure which will store two cross border pointers
* @return A constant of #match_result type
*/
static int check_edge_case(const struct iovec *from, const struct iovec *to, const struct iovec *marker, struct crb_ptrs *crbp)
{
unsigned char *start_ptr = from->iov_base + from->iov_len - marker->iov_len;
unsigned char *end_ptr = from->iov_base + from->iov_len;
unsigned char *marker_ptr = marker->iov_base;
unsigned int bytes_processed = 0;
int match = 0;
// search for the first part of marker in the end of *from* array
while (start_ptr <= end_ptr) {
if (*start_ptr == *marker_ptr && !match) {
crbp->first_buff.iov_base = start_ptr;
crbp->first_buff.iov_len = end_ptr - start_ptr;
match = 1;
}
if (*start_ptr == *marker_ptr && match) {
marker_ptr++;
bytes_processed++;
} else {
break;
}
start_ptr++;
}
if (start_ptr != end_ptr) {
// match not found in the end of *from* array
return MATCH_NOT_FOUND;
}
// search for the second part of marker in the beginning of *to* array
start_ptr = to->iov_base;
end_ptr = to->iov_base + (marker->iov_len - bytes_processed);
while (start_ptr <= end_ptr) {
if (*start_ptr++ != *marker_ptr++)
break;
}
if (start_ptr != end_ptr) {
// match not found in the beginning of *to* array
return MATCH_NOT_FOUND;
}
crbp->second_buff.iov_base = to->iov_base;
crbp->second_buff.iov_len = marker->iov_len - bytes_processed;
return MATCH_FOUND;
}
/**
* @brief Send mmaped buffer over opened socket
* @param[in] sockfd opened socket descriptor
* @param[in] buff pointer to memory mapped buffer
* @param[in] sz the size of @e buff
* @return None
*/
static void send_buffer(int sockfd, unsigned char *buff, size_t sz)
{
size_t bytes_left = sz;
ssize_t bytes_written = 0;
size_t offset = 0;
while (bytes_left > 0) {
bytes_written = write(sockfd, &buff[offset], bytes_left);
if (bytes_written < 0) {
perror(__func__);
return;
}
bytes_left -= bytes_written;
offset += bytes_written;
}
}
/**
* @brief Send file pointed by disk index node over opened socket
* @param[in,out] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[in] indx disk index directory node
* @param[in] sockfd opened socket descriptor
* @return 0 in case disk index node was sent successfully and -1 otherwise
*/
static int send_file(rawdev_buffer *rawdev, struct disk_index *indx, int sockfd)
{
uint64_t mm_file_start;
struct range mmap_range;
mmap_range.from = indx->f_offset & PAGE_BOUNDARY_MASK;
mmap_range.to = indx->f_offset + indx->f_size;
mm_file_start = indx->f_offset - mmap_range.from;
if (mmap_disk(rawdev, &mmap_range) == 0) {
send_buffer(sockfd, &rawdev->disk_mmap[mm_file_start], indx->f_size);
if (munmap_disk(rawdev) != 0) {
D0(fprintf(debug_file, "Unable to unmap memory region\n"));
return -1;
}
} else {
D0(fprintf(debug_file, "Unable to map disk to memory region:"
"disk region start = 0x%llx, disk region end = 0x%llx\n", mmap_range.from, mmap_range.to));
return -1;
}
return 0;
}
/**
* @brief Map a piece of raw device buffer to memory
* @param[in,out] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[in] range pointer to #range structure holding the offsets of
* mmaped region
* @return 0 in case the region was mmaped successfully and -1 in case of an error
*/
static int mmap_disk(rawdev_buffer *rawdev, const struct range *range)
{
int ret = 0;
size_t mmap_size = range->to - range->from;
rawdev->rawdev_fd = open(rawdev->rawdev_path, O_RDONLY);
if (rawdev->rawdev_fd < 0) {
return -1;
}
rawdev->disk_mmap = mmap(0, mmap_size, PROT_READ, MAP_SHARED, rawdev->rawdev_fd, range->from);
if (rawdev->disk_mmap == MAP_FAILED) {
rawdev->disk_mmap = NULL;
close(rawdev->rawdev_fd);
return -1;
}
rawdev->mmap_offset = range->from;
rawdev->mmap_current_size = mmap_size;
return ret;
}
/**
* @brief Unmap currently mapped raw device buffer
* @param[in,out] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @return 0 in case the region was unmapped successfully and -1 in case of an error
*/
static int munmap_disk(rawdev_buffer *rawdev)
{
if (rawdev->disk_mmap == NULL)
return 0;
if (munmap(rawdev->disk_mmap, rawdev->mmap_current_size) != 0)
return -1;
if (close(rawdev->rawdev_fd) != 0)
return -1;
rawdev->mmap_offset = 0;
rawdev->disk_mmap = NULL;
return 0;
}
/**
* @brief Check if the file pointed by disk index node is in the memory mapped region
* @param[in] range pointer to #range structure holding the offsets of
* memory mapped region
* @param[in] indx pointer to the disk index node which should be checked for
* presence in currently mapped region
* @return @b true if the file is the region and @b false otherwise
*/
static bool is_in_range(struct range *range, struct disk_index *indx)
{
if (indx->f_offset >= range->from &&
indx->f_offset <= range->to &&
(indx->f_offset + indx->f_size) <= range->to)
return true;
else
return false;
}
/**
* @brief Prepare socket for communication
* @param[out] socket_fd pointer to socket descriptor
* @param[in] port_num socket port number
* @return None
*/
static void prep_socket(int *socket_fd, uint16_t port_num)
{
int opt = 1;
struct sockaddr_in sock;
memset((char *)&sock, 0, sizeof(struct sockaddr_in));
sock.sin_family = AF_INET;
sock.sin_port = htons(port_num);
*socket_fd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(*socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
bind(*socket_fd, (struct sockaddr *) &sock, sizeof(struct sockaddr_in));
listen(*socket_fd, 10);
}
/**
* @brief Parse command line
* @param[in] cmd pointer to command line buffer, this pointer is
* updated to point to current command
* @return Positive command number from #socket_commands, -1 if command not
* recognized and -2 to indicate that the command buffer has been fully processed
*/
static int parse_command(char **cmd)
{
size_t cmd_len;
int cmd_indx = -1;
char *char_ptr;
D6(fprintf(debug_file, "Parsing command line: %s\n", *cmd));
char_ptr = strpbrk(*cmd, CMD_DELIMITER);
if (char_ptr != NULL) {
char_ptr[0] = '\0';
char_ptr++;
for (int i = 0; i < sizeof(cmd_list) / sizeof(cmd_list[0]); i++) {
cmd_len = strlen(cmd_list[i]);
if (strncmp(char_ptr, cmd_list[i], cmd_len) == 0) {
cmd_indx = i;
break;
}
}
*cmd = char_ptr;
} else {
cmd_indx = -2;
}
return cmd_indx;
}
/**
* @brief Break HTTP GET string after the command part as we do not need that part. The function
* finds the first space character after the command part starts and replaces it with null.
* @param[in,out] cmd pointer to HTTP GET string
* @param[in] cmd_len the length of the command in buffer
* @return None
*/
static void trim_command(char *cmd, ssize_t cmd_len)
{
char *ptr_start, *ptr_end;
if (cmd_len >= 0 && cmd_len < CMD_BUFF_LEN)
cmd[cmd_len] = '\0';
ptr_start = strpbrk(cmd, CMD_DELIMITER);
if (ptr_start) {
ptr_end = strchr(ptr_start, ' ');
if (ptr_end)
ptr_end[0] = '\0';
}
}
/**
* @brief Send a file crossing raw device buffer boundary.
* Such a file is split into two parts, the one in the end of the buffer and the other
* in the beginning, and should be sent in two steps
* @param[in] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[in] indx pointer to the disk index node
* @param[in] sockfd opened socket descriptor
* @return None
*/
static void send_split_file(rawdev_buffer *rawdev, struct disk_index *indx, int sockfd)
{
ssize_t rcntr = 0;
ssize_t scntr = 0;
size_t head_sz = rawdev->end_pos - indx->f_offset;
size_t tail_sz = indx->f_size - head_sz;
uint64_t curr_pos = lseek64(rawdev->rawdev_fd, 0, SEEK_CUR);
unsigned char *buff = malloc(indx->f_size);
if (buff == NULL)
return;
lseek64(rawdev->rawdev_fd, indx->f_offset, SEEK_SET);
while (rcntr < head_sz && rcntr != -1)
rcntr += read(rawdev->rawdev_fd, &buff[rcntr], head_sz - rcntr);
rcntr = 0;
lseek64(rawdev->rawdev_fd, rawdev->start_pos, SEEK_SET);
while (rcntr < tail_sz && rcntr != -1)
rcntr += read(rawdev->rawdev_fd, &buff[head_sz + rcntr], tail_sz - rcntr);
while (scntr < indx->f_size && scntr != -1)
scntr += write(sockfd, &buff[scntr], head_sz - scntr);
fprintf(debug_file, "%s: %d bytes sent\n", __func__, scntr);
lseek64(rawdev->rawdev_fd, curr_pos, SEEK_SET);
}
/**
* @brief Send the number of files (or disk chunks) found over socket connection
* @param[in] sockfd opened socket descriptor
* @param[in] num the number to be sent
* @return None
*/
static void send_fnum(int sockfd, size_t num)
{
char buff[SMALL_BUFF_LEN] = {0};
int len;
len = snprintf(buff, SMALL_BUFF_LEN - 1, "Number of files: %d\n", num);
buff[len] = '\0';
write(sockfd, buff, len);
}
/**
* @brief Read file parameters from a string and fill in disk index node structure
* @param[in] cmd pointer to a string with file parameters
* @param[out] indx pointer to disk index node structure
* @return The number of parameters extracted from the string or -1 in case of an error
*/
static int get_indx_args(char *cmd, struct disk_index *indx)
{
char *cmd_start = strchr(cmd, ':');
if (cmd_start == NULL)
return -1;
return sscanf(++cmd_start, INDEX_FORMAT_STR, &indx->port, &indx->rawtime, &indx->usec, &indx->f_offset, &indx->f_size);
}
/**
* @brief Read time stamp from a string and copy time in UNIX format to disk index
* node structure
* @param[in] cmd pointer to a string withe time stamp
* @param[out] indx pointer to disk index node structure
* @return The number of input items matched from the string or -1 in case of an error
*/
static int get_timestamp_args(char *cmd, struct disk_index *indx)
{
int ret;
struct tm tm;
char *cmd_start = strchr(cmd, ':');
if (cmd_start == NULL)
return -1;
ret = sscanf(++cmd_start, EXIF_TIMESTAMP_FORMAT, &tm.tm_year, &tm.tm_mon,
&tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
tm.tm_year -= 1900;
tm.tm_mon -= 1;
indx->rawtime = mktime(&tm);
return ret;
}
/**
* @brief Define memory mapped disk window where files will be searched
* @param[in] r disk offsets range where memory mapped window will be located
* @param[out] s search window
* @return 0 if the window was successfully located in the region specified and
* -1 in case of an error
*/
static int get_search_window(const struct range *r, struct range *s)
{
if ((r->to - r->from) < SEARCH_SIZE_WINDOW || r->from > r->to)
return -1;
uint64_t middle = (r->to + r->from) / 2;
s->from = (middle - SEARCH_SIZE_WINDOW / 2) & PAGE_BOUNDARY_MASK;
s->to = middle + SEARCH_SIZE_WINDOW / 2;
return 0;
}
/**
* @brief Find JPEG file in a specified window. The window should be large enough to
* contain at least one file.
* @param[in] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[in] wnd pointer to a structure containing the offsets of a search window
* @param[out] indx disk index structure which will hold the offset and size of a file
* @return 0 if a file was found and -1 otherwise
*/
static int find_in_window(rawdev_buffer *rawdev, const struct range *wnd, struct disk_index **indx)
{
int ret = -1;
int pos_start, pos_stop;
if (mmap_disk(rawdev, (const struct range *)wnd) == 0) {
pos_start = find_marker(rawdev->disk_mmap, rawdev->mmap_current_size, elphel_st.iov_base, elphel_st.iov_len, 0);
if (pos_start >= 0) {
rawdev->file_start = rawdev->mmap_offset + pos_start;
if (read_index(rawdev, indx) == 0) {
pos_stop = find_marker(rawdev->disk_mmap + pos_start, rawdev->mmap_current_size - pos_start,
elphel_en.iov_base, elphel_en.iov_len, 1);
stop_index(*indx, rawdev->mmap_offset + pos_stop + pos_start);
ret = 0;
}
}
munmap_disk(rawdev);
} else {
D0(fprintf(debug_file, "ERROR: can not memory map region from 0x%llx to 0x%llx\n", wnd->from, wnd->to));
}
return ret;
}
/**
* @brief Find a file on disk having time stamp close to the time stamp given.
* @param[in] rawdev pointer to #rawdev_buffer structure containing
* the current state of raw device buffer
* @param[in] idir pointer to sparse disk index directory where indexes are sorted
* in time order
* @param[in] rawtime time (in UNIX format) of a possible index candidate
* @return A pointer to disk index node found or NULL if there were no close
* index candidates
*/
static struct disk_index *find_disk_index(rawdev_buffer *rawdev, struct disk_idir *idir, time_t *rawtime)
{
bool process = true;
struct range range;
struct range search_window;
struct disk_index *indx_found = NULL;
struct disk_index *indx_ret = NULL;
struct disk_index *nearest_indx = find_nearest_by_time((const struct disk_idir *)idir, *rawtime);
// define disk offsets where search will be performed
if (nearest_indx == NULL) {
range.from = rawdev->start_pos;
range.to = rawdev->end_pos;
} else {
if (*rawtime > nearest_indx->rawtime) {
range.from = nearest_indx->f_offset;
if (nearest_indx->next != NULL)
range.to = nearest_indx->next->f_offset;
else
range.to = rawdev->end_pos;
} else {
range.to = nearest_indx->f_offset;
if (nearest_indx->prev != NULL)
range.from = nearest_indx->prev->f_offset;
else
range.from = rawdev->start_pos;
}
}
D6(fprintf(debug_file, "Starting search in range: from 0x%llx, to 0x%llx\n", range.from, range.to));
while (process && get_search_window(&range, &search_window) == 0) {
indx_found = NULL;
if (find_in_window(rawdev, &search_window, &indx_found) == 0) {
double time_diff = difftime(indx_found->rawtime, *rawtime);
if (fabs(time_diff) > SEARCH_TIME_WINDOW) {
// the index found is not within search time window, update sparse index directory and
// define a new search window
if (time_diff > 0) {
range.to = search_window.from;
} else {
range.from = search_window.to;
}
} else {
// the index found is within search time window, stop search and return
process = false;
indx_ret = indx_found;
}
insert_node(idir, indx_found);
} else {
// index is not found in the search window, move toward the start of the range
range.to = search_window.from;
}
}
D6(fprintf(debug_file, "\nSparse index directory dump, %d nodes:\n", idir->size));
dump_index_dir(idir);
return indx_ret;
}
/**
* @brief Raw device buffer reading function.
*
* This function is started in a separated thread right after the application has started. It opens a
* communication socket, waits for commands sent over the socket and process them.
* @param[in, out] arg pointer to #camogm_state structure
* @return None
* @warning The main processing loop of the function is enclosed in @e pthread_cleanup_push and @e pthread_cleanup_pop
* calls. The effect of use of normal @b return or @b break to prematurely leave this loop is undefined.
* @todo Print unrecognized command to debug output file
*/
void *reader(void *arg)
{
int sockfd, fd;
int disk_chunks;
int cmd;
char cmd_buff[CMD_BUFF_LEN] = {0};
char *cmd_ptr;
char send_buff[CMD_BUFF_LEN] = {0};
bool transfer;
ssize_t cmd_len;
size_t mm_file_start, mm_file_size;
size_t file_cntr;
camogm_state *state = (camogm_state *)arg;
rawdev_buffer *rawdev = &state->rawdev;
struct range mmap_range;
struct disk_index *disk_indx, *cross_boundary_indx;
struct disk_idir index_dir;
struct disk_idir index_sparse;
struct exit_state exit_state = {
.state = state,
.idir = &index_dir,
.sparse_idir = &index_sparse,
.sockfd_const = &sockfd,
.sockfd_temp = &fd
};
memset(&index_dir, 0, sizeof(struct disk_index));
memset(&index_sparse, 0, sizeof(struct disk_index));
prep_socket(&sockfd, state->sock_port);
pthread_cleanup_push(exit_thread, &exit_state);
while (true) {
fd = accept(sockfd, NULL, 0);
if (fd == -1)
continue;
if (state->prog_state == STATE_STOPPED && state->rawdev_op) {
pthread_mutex_lock(&state->mutex);
state->prog_state = STATE_READING;
pthread_mutex_unlock(&state->mutex);
} else {
close(fd);
D0(fprintf(debug_file, "Can not change state of the program, check settings\n"));
continue;
}
cmd_len = read(fd, cmd_buff, sizeof(cmd_buff) - 1);
cmd_ptr = cmd_buff;
trim_command(cmd_ptr, cmd_len);
while ((cmd = parse_command(&cmd_ptr)) != -2 && state->rawdev.thread_state != STATE_CANCEL) {
if (cmd >= 0)
D6(fprintf(debug_file, "Got command '%s', number %d\n", cmd_list[cmd], cmd));
switch (cmd) {
case CMD_BUILD_INDEX:
// scan raw device buffer and create disk index directory
if (index_dir.size != 0) {
delete_idir(&index_dir);
}
build_index(state, &index_dir);
D3(fprintf(debug_file, "%d files read from %s\n", index_dir.size, state->rawdev.rawdev_path));
break;
case CMD_GET_INDEX:
// send the content of disk index directory over socket
if (index_dir.size > 0) {
int len;
disk_indx = index_dir.head;
while (disk_indx != NULL) {
len = snprintf(send_buff, CMD_BUFF_LEN - 1, INDEX_FORMAT_STR,
disk_indx->port, disk_indx->rawtime, disk_indx->usec, disk_indx->f_offset, disk_indx->f_size);
send_buff[len] = '\0';
write(fd, send_buff, len);
disk_indx = disk_indx->next;
}
} else {
D0(fprintf(debug_file, "Index directory does not contain any files. Try to rebuild index "
"directory with 'build_index' command\n"));
}
break;
case CMD_READ_DISK:
// mmap raw device buffer in MMAP_CHUNK_SIZE chunks and send them over socket
mmap_range.from = rawdev->start_pos & PAGE_BOUNDARY_MASK;
mmap_range.to = mmap_range.from + rawdev->mmap_default_size;
disk_chunks = (size_t)ceil((double)(rawdev->end_pos - rawdev->start_pos) / (double)rawdev->mmap_default_size);
transfer = true;
mm_file_start = rawdev->start_pos;
mm_file_size = rawdev->mmap_default_size - rawdev->start_pos;
send_fnum(fd, disk_chunks);
close(fd);
while (disk_chunks > 0 && transfer && state->rawdev.thread_state != STATE_CANCEL) {
fd = accept(sockfd, NULL, 0);
if (mmap_disk(rawdev, &mmap_range) == 0) {
send_buffer(fd, &rawdev->disk_mmap[mm_file_start], mm_file_size);
} else {
transfer = false;
D0(fprintf(debug_file, "Unable to map disk to memory region:"
"disk region start = 0x%llx, disk region end = 0x%llx\n", mmap_range.from, mmap_range.to));
}
if (munmap_disk(rawdev) != 0) {
transfer = false;
D0(fprintf(debug_file, "Unable to unmap memory region\n"));
}
mm_file_start = 0;
mm_file_size = rawdev->mmap_default_size;
disk_chunks--;
mmap_range.from = mmap_range.to;
mmap_range.to = mmap_range.from + rawdev->mmap_default_size;
if (mmap_range.to > rawdev->end_pos) {
mmap_range.to = rawdev->end_pos;
mm_file_size = mmap_range.to - mmap_range.from;
}
close(fd);
}
break;
case CMD_READ_FILE:
// read single file by offset given
if (index_dir.size > 0) {
struct disk_index indx;
if (get_indx_args(cmd_ptr, &indx) > 0 &&
(disk_indx = find_by_offset(&index_dir, indx.f_offset)) != NULL){
send_file(rawdev, disk_indx, fd);
}
}
break;
case CMD_FIND_FILE: {
// find file by time stamp
struct disk_index indx;
struct disk_index *indx_ptr = NULL;
if (get_timestamp_args(cmd_ptr, &indx) > 0) {
if (index_dir.size == 0) {
indx_ptr = find_disk_index(rawdev, &index_sparse, &indx.rawtime);
if (indx_ptr != NULL)
index_sparse.curr_indx = indx_ptr;
} else {
indx_ptr = find_nearest_by_time(&index_dir, indx.rawtime);
}
if (indx_ptr != NULL)
send_file(rawdev, indx_ptr, fd);
}
break;
}
case CMD_NEXT_FILE: {
// read next file after previously found file
struct range rng;
struct disk_index *new_indx = NULL;
struct disk_index *indx_ptr = NULL;
uint64_t len;
if (index_sparse.curr_indx != NULL) {
if (index_sparse.curr_indx->next != NULL) {
len = index_sparse.curr_indx->next->f_offset - index_sparse.curr_indx->f_offset - 1;
if (len > 0) {
rng.from = index_sparse.curr_indx->f_offset + index_sparse.curr_indx->f_size + 1;
rng.to = index_sparse.curr_indx->next->f_offset;
} else {
indx_ptr = index_sparse.curr_indx->next;
}
} else {
rng.from = index_sparse.curr_indx->f_offset + index_sparse.curr_indx->f_size;
rng.to = rawdev->end_pos;
}
if (indx_ptr == NULL) {
rng.from &= PAGE_BOUNDARY_MASK;
if (rng.to - rng.from > rawdev->mmap_default_size)
rng.to = rng.from + rawdev->mmap_default_size;
if (find_in_window(rawdev, &rng, &new_indx) == 0) {
insert_next(&index_sparse, index_sparse.curr_indx, new_indx);
send_file(rawdev, new_indx, fd);
index_sparse.curr_indx = new_indx;
}
} else {
send_file(rawdev, indx_ptr, fd);
}
}
break;
}
case CMD_PREV_FILE: {
break;
}
case CMD_READ_ALL_FILES:
// read files from raw device buffer and send them over socket; the disk index directory
// should be built beforehand
if (index_dir.size > 0) {
send_fnum(fd, index_dir.size);
close(fd);
mmap_range.from = rawdev->start_pos;
mmap_range.to = rawdev->start_pos + rawdev->mmap_default_size;
disk_indx = index_dir.head;
cross_boundary_indx = NULL;
file_cntr = 0;
transfer = true;
while (file_cntr < index_dir.size && disk_indx != NULL && state->rawdev.thread_state != STATE_CANCEL) {
if (is_in_range(&mmap_range, disk_indx) && rawdev->disk_mmap != NULL) {
fd = accept(sockfd, NULL, 0);
mm_file_start = disk_indx->f_offset - rawdev->mmap_offset;
send_buffer(fd, &rawdev->disk_mmap[mm_file_start], disk_indx->f_size);
close(fd);
disk_indx = disk_indx->next;
file_cntr++;
} else {
if (munmap_disk(rawdev) == 0) {
mmap_range.from = disk_indx->f_offset & PAGE_BOUNDARY_MASK;
mmap_range.to = mmap_range.from + rawdev->mmap_default_size;
if (mmap_range.to > rawdev->end_pos) {
mmap_range.to = rawdev->end_pos;
}
if (disk_indx->f_offset + disk_indx->f_size <= mmap_range.to) {
if (mmap_disk(rawdev, &mmap_range) < 0) {
disk_indx = NULL;
D0(fprintf(debug_file, "Unable to map disk to memory region:"
" disk region start = 0x%llx, disk region end = 0x%llx\n", mmap_range.from, mmap_range.to));
}
} else {
cross_boundary_indx = disk_indx;
disk_indx = NULL;
}
} else {
disk_indx = NULL;
D0(fprintf(debug_file, "Unable to unmap memory region\n"));
}
}
}
munmap_disk(rawdev);
if (cross_boundary_indx != NULL) {
send_split_file(rawdev, cross_boundary_indx, fd);
close(fd);
}
} else {
D0(fprintf(debug_file, "Index directory does not contain any files. Try to rebuild index "
"directory with 'build_index' command\n"));
}
break;
case CMD_STATUS:
break;
default:
D0(fprintf(debug_file, "Unrecognized command is skipped\n"));
}
}
if (is_fd_valid(fd))
close(fd);
pthread_mutex_lock(&state->mutex);
state->prog_state = STATE_STOPPED;
pthread_mutex_unlock(&state->mutex);
usleep(COMMAND_LOOP_DELAY);
}
pthread_cleanup_pop(0);
return (void *) 0;
}
/**
* @brief Clean up after the reading thread is closed. This function is thread-cancellation handler and it is
* passed to @e pthread_cleanup_push() function.
* @param[in] arg pointer to #exit_state structure containing resources that
* should be closed
* @return None
*/
static inline void exit_thread(void *arg)
{
struct exit_state *s = (struct exit_state *)arg;
if (s->state->rawdev.disk_mmap != NULL)
munmap(s->state->rawdev.disk_mmap, s->state->rawdev.mmap_current_size);
if (is_fd_valid(s->state->rawdev.rawdev_fd))
close(s->state->rawdev.rawdev_fd);
if (s->idir->size != 0)
delete_idir(s->idir);
if (s->sparse_idir-> size != 0)
delete_idir(s->sparse_idir);
if (is_fd_valid(*s->sockfd_const))
close(*s->sockfd_const);
if (is_fd_valid(*s->sockfd_temp))
close(*s->sockfd_temp);
}
/**
* @brief Extract the position and parameters of JPEG files in raw device buffer and
* build disk index directory for further file extraction.
*
* Data from raw device is read to a buffer in #PHY_BLK_SZ blocks. The buffer is
* then analyzed for JPEG markers stored in #elphelst and #elphelen arrays. The offsets and
* sizes of the files found in the buffer are recorded to disk index directory.
* @param[in] state a pointer to a structure containing current state
* @param[out] idir a pointer to disk index directory. This directory will contain
* offset of the files found in the raw device buffer.
* @return None
*/
static void build_index(camogm_state *state, struct disk_idir *idir)
{
const int include_markers = INCLUDE_MARKERS;
int process;
int zero_cross;
int err;
int pos_start, pos_stop;
int buff_processed;
int search_state;
int idir_result;
ssize_t rd;
unsigned char buff[PHY_BLK_SZ];
unsigned char next_buff[PHY_BLK_SZ];
unsigned char *active_buff = buff;
unsigned char *save_from = NULL;
unsigned char *save_to = NULL;
uint64_t dev_curr_pos = 0;
uint64_t include_st_marker, include_en_marker;
size_t add_stm_len, add_enm_len;
struct disk_index *node = NULL;
state->rawdev.rawdev_fd = open(state->rawdev.rawdev_path, O_RDONLY);
if (state->rawdev.rawdev_fd < 0) {
D0(perror(__func__));
D0(fprintf(debug_file, "Error opening raw device %s\n", state->rawdev.rawdev_path));
return;
}
if (include_markers) {
include_st_marker = 0;
add_stm_len = elphel_st.iov_len;
include_en_marker = 1;
add_enm_len = 0;
} else {
include_st_marker = 0;
add_stm_len = 0;
include_en_marker = 1;
add_enm_len = elphel_en.iov_len;
}
process = 1;
zero_cross = 0;
search_state = SEARCH_SKIP;
idir_result = 0;
while (process && state->rawdev.thread_state != STATE_CANCEL) {
rd = read(state->rawdev.rawdev_fd, buff, sizeof(buff));
err = errno;
if ((rd > 0) && (dev_curr_pos + rd > state->rawdev.end_pos)) {
// read pointer jumped over the raw storage buffer end, truncate excessive data
D3(fprintf(debug_file, "End of raw storage buffer is reached, will start from the beginning\n"));
rd = state->rawdev.end_pos - dev_curr_pos;
zero_cross = 1;
lseek64(state->rawdev.rawdev_fd, state->rawdev.start_pos, SEEK_SET);
dev_curr_pos = state->rawdev.start_pos;
if (rd == 0) {
continue;
}
} else if (rd < 0) {
// read error or read pointer exceeded raw storage capacity, close files and terminate
process = 0;
D0(fprintf(debug_file, "Raw device read was unsuccessful: %s\n", strerror(err)));
} else if (rd == 0) {
// end of device file reached
D3(fprintf(debug_file, "End of raw storage device file is reached, will start from the beginning\n"));
zero_cross = 1;
lseek64(state->rawdev.rawdev_fd, state->rawdev.start_pos, SEEK_SET);
dev_curr_pos = state->rawdev.start_pos;
}
if (process) {
save_from = buff;
save_to = buff + rd;
active_buff = buff;
buff_processed = 0;
do {
// process data in read buffer
pos_start = find_marker(save_from, save_to - save_from, elphel_st.iov_base, elphel_st.iov_len, include_st_marker);
pos_stop = find_marker(save_from, save_to - save_from, elphel_en.iov_base, elphel_en.iov_len, include_en_marker);
node = NULL;
if (pos_start == MATCH_NOT_FOUND && pos_stop == MATCH_NOT_FOUND) {
// normal condition, search in progress
buff_processed = 1;
} else if (pos_start >= 0 && pos_stop >= 0 && pos_start > pos_stop) {
// normal condition, start marker following stop marker found - this indicates a new file
if (search_state == SEARCH_FILE_DATA) {
uint64_t disk_pos = dev_curr_pos + pos_stop + (save_from - active_buff);
idir_result = stop_index(idir->tail, disk_pos);
}
if (zero_cross == 0) {
state->rawdev.file_start = dev_curr_pos + pos_start + (save_from - active_buff);
idir_result = read_index(&state->rawdev, &node);
if (idir_result == 0)
add_node(idir, node);
search_state = SEARCH_FILE_DATA;
save_from = save_from + pos_start + add_stm_len;
// @todo: replace with pointer to current buffer
save_to = buff + rd;
} else {
buff_processed = 1;
process = 0;
}
D6(fprintf(debug_file, "State 'stop current file and start new file'\n"));
} else if (pos_start >= 0 && pos_stop == MATCH_NOT_FOUND && search_state == SEARCH_SKIP) {
// normal condition, new file found
search_state = SEARCH_FILE_DATA;
state->rawdev.file_start = dev_curr_pos + pos_start + (save_from - active_buff);
idir_result = read_index(&state->rawdev, &node);
if (idir_result == 0)
add_node(idir, node);
buff_processed = 1;
D6(fprintf(debug_file, "New file found. File start position: %llu\n", state->rawdev.file_start));
D6(fprintf(debug_file, "State 'starting file'\n"));
} else if (pos_start >= 0 && pos_stop == MATCH_NOT_FOUND && search_state == SEARCH_FILE_DATA) {
// error condition (normally should not happen), discard current index and start a new one
buff_processed = 1;
remove_node(idir, idir->tail);
if (zero_cross == 0) {
state->rawdev.file_start = dev_curr_pos + pos_start + (save_from - active_buff);
idir_result = read_index(&state->rawdev, &node);
if (idir_result == 0)
add_node(idir, node);
} else {
process = 0;
}
D6(fprintf(debug_file, "State 'abnormal start marker, remove current disk index from directory and skip data'\n"));
} else if (pos_start == MATCH_NOT_FOUND && pos_stop >= 0 &&
search_state == SEARCH_FILE_DATA) {
// normal condition, save current file size to index directory
uint64_t disk_pos = dev_curr_pos + pos_stop + (save_from - active_buff);
search_state = SEARCH_SKIP;
idir_result = stop_index(idir->tail, disk_pos);
buff_processed = 1;
if (zero_cross)
process = 0;
D6(fprintf(debug_file, "State 'finishing file'\n"));
} else if (pos_start == MATCH_NOT_FOUND && pos_stop >= 0 && search_state == SEARCH_SKIP) {
// error condition (normally should not happen), drop current read buffer and do nothing
buff_processed = 1;
D6(fprintf(debug_file, "State 'abnormal stop marker, skip data'\n"));
} else if (pos_start == MATCH_PARTIAL && search_state == SEARCH_SKIP) {
// partial start marker found in the end of read buffer, get next chunk of data and try to find marker there
enum match_result result;
struct crb_ptrs field_markers;
struct iovec next_chunk = {
.iov_base = next_buff,
};
struct iovec curr_chunk = {
.iov_base = save_from,
.iov_len = save_to - save_from
};
ssize_t next_rd = read(state->rawdev.rawdev_fd, next_buff, sizeof(next_buff));
next_chunk.iov_len = next_rd;
result = check_edge_case(&curr_chunk, &next_chunk, &elphel_st, &field_markers);
if (result == MATCH_FOUND) {
search_state = SEARCH_FILE_DATA;
state->rawdev.file_start = dev_curr_pos + pos_start + (save_from - active_buff);
idir_result = read_index(&state->rawdev, &node);
if (idir_result == 0)
add_node(idir, node);
D6(fprintf(debug_file, "File start position: %llu\n", state->rawdev.file_start));
save_from = next_chunk.iov_base + field_markers.second_buff.iov_len;
save_to = next_chunk.iov_base + next_chunk.iov_len;
} else {
save_from = next_chunk.iov_base;
save_to = next_chunk.iov_base + next_chunk.iov_len;
}
dev_curr_pos += next_rd;
active_buff = next_buff;
D6(fprintf(debug_file, "State 'check elphel_st cross boundary'; result = %d\n", result));
} else if (pos_stop == MATCH_PARTIAL && search_state == SEARCH_FILE_DATA) {
// partial end marker found in the end of read buffer - get next chunk of data and try to find marker there
enum match_result result;
struct crb_ptrs field_markers;
struct iovec next_chunk = {
.iov_base = next_buff,
};
struct iovec curr_chunk = {
.iov_base = save_from,
.iov_len = save_to - save_from
};
ssize_t next_rd = read(state->rawdev.rawdev_fd, next_buff, sizeof(next_buff));
next_chunk.iov_len = next_rd;
result = check_edge_case(&curr_chunk, &next_chunk, &elphel_en, &field_markers);
if (result == MATCH_FOUND) {
search_state = SEARCH_SKIP;
uint64_t disk_pos = dev_curr_pos + pos_stop + (save_from - active_buff);
idir_result = stop_index(idir->tail, disk_pos);
save_from = next_chunk.iov_base + field_markers.second_buff.iov_len;
save_to = next_chunk.iov_base + next_chunk.iov_len;
} else {
save_from = next_chunk.iov_base;
save_to = next_chunk.iov_base + next_chunk.iov_len;
}
dev_curr_pos += next_rd;
active_buff = next_buff;
D6(fprintf(debug_file, "State 'check elphel_en' cross boundary:; result = %d\n", result));
} else {
// no markers found and new file has not bee started yet - skip data
D6(fprintf(debug_file, "Undefined state: pos_start = %d, pos_stop = %d, search_state = %d\n",
pos_start, pos_stop, search_state));
buff_processed = 1;
if (zero_cross)
process = 0;
}
} while (buff_processed == 0 && idir_result == 0);
if (idir_result != 0) {
process = 0;
}
dev_curr_pos += rd;
state->rawdev.curr_pos_r = dev_curr_pos;
}
}
if (close(state->rawdev.rawdev_fd) != 0) {
perror("Unable to close raw device: ");
}
state->rawdev.rawdev_fd = -1;
}
/** @file camogm_read.h
* @brief Provides reading data written to raw device storage and saving the data to a device with file system.
* @copyright Copyright (C) 2016 Elphel, Inc.
*
* <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/>.
*/
#ifndef _CAMOGM_READ_H
#define _CAMOGM_READ_H
#include "camogm.h"
/**
* @struct range
* @brief Container for offsets in raw device buffer
* @var range::from
* Starting offset
* @var range::to
* Ending offset
*/
struct range {
uint64_t from;
uint64_t to;
};
void *reader(void *arg);
#endif /* _CAMOGM_READ_H */
/** @file index_list.c
* @brief Provides data structures and functions for working with disk index directory
* @copyright Copyright (C) 2016 Elphel, Inc.
*
* <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/>.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include "index_list.h"
/**
* @brief Create a new node in a linked list of disk indexes
* @param[in,out] index pointer to a newly allocated structure
* @return 0 in case a new disk index structure was successfully created and -1 otherwise
*/
int create_node(struct disk_index **index)
{
if (*index != NULL)
return -1;
*index = malloc(sizeof(struct disk_index));
if (*index != NULL) {
memset(*index, 0, sizeof(struct disk_index));
return 0;
} else {
return -1;
}
}
/**
* @brief Add a new index node to disk index directory
* @param[in,out] idir pointer to disk index directory
* @param[in] index pointer to index node to be added to disk index directory
* @return The number of entries in disk index directory
*/
int add_node(struct disk_idir *idir, struct disk_index *index)
{
if (idir->head == NULL && idir->tail == NULL) {
idir->head = index;
idir->tail = index;
idir->size = 1;
} else {
index->prev = idir->tail;
idir->tail->next = index;
idir->tail = index;
idir->size++;
}
return idir->size;
}
/**
* @brief Insert new node in chronological order
* @param[in,out] idir index directory to which a new node should be added
* @param[in] indx a pointer to new node
* @return The number of nodes in the directory
*/
int insert_node(struct disk_idir *idir, struct disk_index *indx)
{
int ret = 0;
struct disk_index *node;
if (idir->head == NULL) {
add_node(idir, indx);
return 1;
}
node = idir->head;
while (node != NULL) {
if (indx->rawtime < node->rawtime) {
ret = insert_prev(idir, node, indx);
break;
}
node = node->next;
}
if (node == NULL)
ret = insert_next(idir, idir->tail, indx);
return ret;
}
/**
* @brief Insert new index to the list before the index specified. It means that new index will
* be inserted toward the head.
* @param[in,out] idir pointer to the lined list of indexes
* @param[in] parent pointer to the element before which the new element will be inserted
* @param[in] new_indx pointer to the element which will be inserted
* @return The number of nodes in the list
*/
int insert_prev(struct disk_idir *idir, struct disk_index *parent, struct disk_index *new_indx)
{
if (parent->prev != NULL) {
new_indx->next = parent;
new_indx->prev = parent->prev;
parent->prev->next = new_indx;
parent->prev = new_indx;
} else {
new_indx->next = parent;
new_indx->prev = NULL;
parent->prev = new_indx;
idir->head = new_indx;
}
idir->size++;
return idir->size;
}
/**
* @brief Insert new index to the list after the index specified. It means that new index will
* be inserted toward the tail.
* @param[in,out] idir pointer to the linked list of indexes
* @param[in] parent pointer to the element after which the new element will be inserted
* @param[in] new_indx pointer to the element which will be inserted
* @return The number of nodes in the list
*/
int insert_next(struct disk_idir *idir, struct disk_index *parent, struct disk_index *new_indx)
{
if (parent->next != NULL) {
new_indx->next = parent->next;
new_indx->prev = parent;
parent->next->prev = new_indx;
parent->next = new_indx;
} else {
new_indx->next = NULL;
new_indx->prev = parent;
parent->next = new_indx;
idir->tail = new_indx;
}
idir->size++;
return idir->size;
}
/**
* @brief Find index node by its start offset
* @param[in] idir pointer to disk index directory
* @param[in] offset the offset of the file which should be found
* @return pointer to disk index node or NULL if the corresponding file was not found
*/
struct disk_index *find_by_offset(const struct disk_idir *idir, uint64_t offset)
{
struct disk_index *index = idir->head;
while (index != NULL) {
if (index->f_offset == offset)
break;
index = index->next;
}
return index;
}
/** @brief Find index node by its time stamp
* @param[in] idir pointer to disk index directory
* @param[in] time the time stamp of the file which should be found
* @return pointer to disk index node or NULL if the corresponding file was not found
*/
struct disk_index *find_nearest_by_time(const struct disk_idir *idir, time_t time)
{
struct disk_index *ptr = NULL;
struct disk_index *index = idir->head;
if (idir->size == 0)
return NULL;
if (index->next != NULL)
ptr = index->next;
else
return index;
while (index != NULL) {
if (fabs(difftime(index->rawtime, time)) < fabs(difftime(ptr->rawtime, time)))
ptr = index;
index = index->next;
}
return ptr;
}
/**
* @brief Remove a single index node from disk index directory
* @param[in,out] idir pointer to disk index directory
* @param[in] node pointer to the index node which should be removed
* @return The number of entries in disk index directory
*/
int remove_node(struct disk_idir *idir, struct disk_index *node)
{
if (node == NULL)
return -1;
if (node == idir->head) {
idir->head = node->next;
idir->head->prev = NULL;
} else if (node == idir->tail) {
idir->tail = node->prev;
idir->tail->next = NULL;
} else {
struct disk_index *ind = idir->head;
while (ind != NULL) {
if (ind == node) {
ind->prev->next = ind->next;
ind->next->prev = ind->prev;
break;
}
ind = ind->next;
}
}
free(node);
node = NULL;
idir->size--;
return idir->size;
}
/**
* @brief Remove all entries from disk index directory an free memory
* @param[in] idir pointer to disk index directory
* @return 0 in case the directory was successfully deleted and -1 if the directory was empty
*/
int delete_idir(struct disk_idir *idir)
{
struct disk_index *curr_ind;
struct disk_index *next_ind;
if (idir == NULL || idir->head == NULL)
return -1;
curr_ind = idir->head;
next_ind = curr_ind->next;
while (curr_ind != NULL) {
free(curr_ind);
curr_ind = next_ind;
if (curr_ind != NULL)
next_ind = curr_ind->next;
}
idir->head = idir->tail = NULL;
idir->size = 0;
return 0;
}
/** @file index_list.h
* @brief Provides data structures and functions for working with disk index directory
* @copyright Copyright (C) 2016 Elphel, Inc.
*
* <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/>.
*/
#ifndef _INDEX_LIST_H
#define _INDEX_LIST_H
#include <stdint.h>
/**
* @struct disk_index
* @brief Contains a single entry into disk index directory. Each node in
* the disk index directory corresponds to a file in the raw device buffer and
* hold its starting offset, sensor port number, time stamp and file size.
* @var disk_index::next
* Pointer to the next index node
* @var disk_index::prev
* Pointer to the previous disk index node
* @var disk_index::rawtime
* Time stamp in UNIX format
* @var disk_index::usec
* The microsecond part of the time stamp
* @var disk_index::port
* The sensor port number this frame was captured from
* @var disk_index::f_size
* File size in bytes
* @var disk_index::f_offset
* The offset of the file start in the raw device buffer (in bytes)
*/
struct disk_index {
struct disk_index *next;
struct disk_index *prev;
time_t rawtime;
unsigned int usec;
uint32_t port;
size_t f_size;
uint64_t f_offset;
};
/**
* @struct disk_idir
* @brief Contains pointers to disk index directory
* @var disk_idir::head
* Pointer to the first node of disk index directory
* @var disk_idir::tail
* Pointer to the last node of disk index directory
* @var disk_idir::size
* The number of nodes in disk index directory
*/
struct disk_idir {
struct disk_index *head;
struct disk_index *tail;
struct disk_index *curr_indx;
size_t size;
};
void dump_index_dir(const struct disk_idir *idir);
int create_node(struct disk_index **index);
int add_node(struct disk_idir *idir, struct disk_index *index);
int insert_prev(struct disk_idir *idir, struct disk_index *parent, struct disk_index *new_indx);
int insert_next(struct disk_idir *idir, struct disk_index *parent, struct disk_index *new_indx);
int insert_node(struct disk_idir *idir, struct disk_index *indx);
struct disk_index *find_by_offset(const struct disk_idir *idir, uint64_t offset);
struct disk_index *find_nearest_by_time(const struct disk_idir *idir, time_t time);
int remove_node(struct disk_idir *idir, struct disk_index *node);
int delete_idir(struct disk_idir *idir);
#endif /* _INDEX_LIST_H */
/** @file ogmstreams.h */
#ifndef __OGGSTREAMS_H
#define __OGGSTREAMS_H
/*
/**
* Taken from http://tobias.everwicked.com/packfmt.htm
*
First packet (header)
---------------------
pos | content | description
-------+-------------------------+----------------------------------
0x0000 | 0x01 | indicates 'header packet'
-------+-------------------------+----------------------------------
0x0001 | stream_header | the size is indicated in the
| | size member
Second packet (comment)
-----------------------
pos | content | description
-------+-------------------------+----------------------------------
0x0000 | 0x03 | indicates 'comment packet'
-------+-------------------------+----------------------------------
0x0001 | data | see vorbis doc on www.xiph.org
Data packets
------------
pos | content | description
---------+-------------------------+----------------------------------
0x0000 | Bit0 0 | indicates data packet
| Bit1 Bit 2 of lenbytes |
| Bit2 unused |
| Bit3 keyframe |
| Bit4 unused |
| Bit5 unused |
| Bit6 Bit 0 of lenbytes |
| Bit7 Bit 1 of lenbytes |
---------+-------------------------+----------------------------------
0x0001 | LowByte | Length of this packet in samples
| ... | (frames for video, samples for
| HighByte | audio, 1ms units for text)
---------+-------------------------+----------------------------------
0x0001+ | data | packet contents
lenbytes | |
*
* First packet (header)
* ---------------------
*
* pos | content | description
* -------|-------------------------|----------------------------------
* 0x0000 | 0x01 | indicates 'header packet'
* 0x0001 | stream_header | the size is indicated in the
* &nbsp; | &nbsp; | size member
*
*
* Second packet (comment)
* -----------------------
*
* pos | content | description
* -------|-------------------------|----------------------------------
* 0x0000 | 0x03 | indicates 'comment packet'
* 0x0001 | data | see vorbis doc on www.xiph.org
*
*
* Data packets
* ------------
*
* pos | content | description
* ---------|-------------------------|----------------------------------
* 0x0000 | Bit0 0 | indicates data packet
* &nbsp; | Bit1 Bit 2 of lenbytes | &nbsp;
* &nbsp; | Bit2 unused | &nbsp;
* &nbsp; | Bit3 keyframe | &nbsp;
* &nbsp; | Bit4 unused | &nbsp;
* &nbsp; | Bit5 unused | &nbsp;
* &nbsp; | Bit6 Bit 0 of lenbytes | &nbsp;
* &nbsp; | Bit7 Bit 1 of lenbytes | &nbsp;
* 0x0001 | LowByte | Length of this packet in samples
* &nbsp; | ... | (frames for video, samples for
* &nbsp; | HighByte | audio, 1ms units for text)
* 0x0001+ | data | packet contents
* lenbytes | &nbsp; | &nbsp;
*
*/
//// OggDS headers
// Header for the new header format
/** OggDS headers */
/** Header for the new header format */
typedef struct stream_header_video {
ogg_int32_t width;
ogg_int32_t height;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment