You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
12 KiB
C
396 lines
12 KiB
C
/***************************************************************************
|
|
* Generic routines to operate on miniSEED records.
|
|
*
|
|
* This file is part of the miniSEED Library.
|
|
*
|
|
* Copyright (c) 2023 Chad Trabant, EarthScope Data Services
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
***************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "libmseed.h"
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Initialize and return an ::MS3Record
|
|
*
|
|
* Memory is allocated if for a new ::MS3Record if \a msr is NULL.
|
|
*
|
|
* If memory for the \c datasamples field has been allocated the pointer
|
|
* will be retained for reuse. If memory for extra headers has been
|
|
* allocated it will be released.
|
|
*
|
|
* @param[in] msr A ::MS3Record to re-initialize
|
|
*
|
|
* @returns a pointer to a ::MS3Record struct on success or NULL on error.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
MS3Record *
|
|
msr3_init (MS3Record *msr)
|
|
{
|
|
void *datasamples = NULL;
|
|
size_t datasize = 0;
|
|
|
|
if (!msr)
|
|
{
|
|
msr = (MS3Record *)libmseed_memory.malloc (sizeof (MS3Record));
|
|
}
|
|
else
|
|
{
|
|
datasamples = msr->datasamples;
|
|
datasize = msr->datasize;
|
|
|
|
if (msr->extra)
|
|
libmseed_memory.free (msr->extra);
|
|
}
|
|
|
|
if (msr == NULL)
|
|
{
|
|
ms_log (2, "Cannot allocate memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
memset (msr, 0, sizeof (MS3Record));
|
|
|
|
msr->datasamples = datasamples;
|
|
msr->datasize = datasize;
|
|
|
|
msr->reclen = -1;
|
|
msr->samplecnt = -1;
|
|
msr->encoding = -1;
|
|
|
|
return msr;
|
|
} /* End of msr3_init() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Free all memory associated with a ::MS3Record
|
|
*
|
|
* Free all memory associated with a ::MS3Record, including extra
|
|
* header and data samples if present.
|
|
*
|
|
* @param[in] ppmsr Pointer to point of the ::MS3Record to free
|
|
***************************************************************************/
|
|
void
|
|
msr3_free (MS3Record **ppmsr)
|
|
{
|
|
if (ppmsr != NULL && *ppmsr != 0)
|
|
{
|
|
if ((*ppmsr)->extra)
|
|
libmseed_memory.free ((*ppmsr)->extra);
|
|
|
|
if ((*ppmsr)->datasamples)
|
|
libmseed_memory.free ((*ppmsr)->datasamples);
|
|
|
|
libmseed_memory.free (*ppmsr);
|
|
|
|
*ppmsr = NULL;
|
|
}
|
|
} /* End of msr3_free() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Duplicate a ::MS3Record
|
|
*
|
|
* Extra headers are duplicated as well.
|
|
*
|
|
* If the \a datadup flag is true (non-zero) and the source
|
|
* ::MS3Record has associated data samples copy them as well.
|
|
*
|
|
* @param[in] msr ::MS3Record to duplicate
|
|
* @param[in] datadup Flag to control duplication of data samples
|
|
*
|
|
* @returns Pointer to a new ::MS3Record on success and NULL on error
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
MS3Record *
|
|
msr3_duplicate (const MS3Record *msr, int8_t datadup)
|
|
{
|
|
MS3Record *dupmsr = 0;
|
|
|
|
if (!msr)
|
|
{
|
|
ms_log (2, "Required argument not defined: 'msr'\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate target MS3Record structure */
|
|
if ((dupmsr = msr3_init (NULL)) == NULL)
|
|
return NULL;
|
|
|
|
/* Copy MS3Record structure */
|
|
memcpy (dupmsr, msr, sizeof (MS3Record));
|
|
|
|
/* Disconnect pointers from the source structure and reference values */
|
|
dupmsr->extra = NULL;
|
|
dupmsr->extralength = 0;
|
|
dupmsr->datasamples = NULL;
|
|
dupmsr->datasize = 0;
|
|
dupmsr->numsamples = 0;
|
|
|
|
/* Copy extra headers */
|
|
if (msr->extralength > 0 && msr->extra)
|
|
{
|
|
/* Allocate memory for new FSDH structure */
|
|
if ((dupmsr->extra = (char *)libmseed_memory.malloc (msr->extralength)) == NULL)
|
|
{
|
|
ms_log (2, "Error allocating memory\n");
|
|
msr3_free (&dupmsr);
|
|
return NULL;
|
|
}
|
|
|
|
memcpy (dupmsr->extra, msr->extra, msr->extralength);
|
|
|
|
if (dupmsr->extra)
|
|
dupmsr->extralength = msr->extralength;
|
|
}
|
|
|
|
/* Copy data samples if requested and available */
|
|
if (datadup && msr->numsamples > 0 && msr->datasize > 0 && msr->datasamples)
|
|
{
|
|
/* Allocate memory for new data array */
|
|
if ((dupmsr->datasamples = libmseed_memory.malloc ((size_t) (msr->datasize))) == NULL)
|
|
{
|
|
ms_log (2, "Error allocating memory\n");
|
|
msr3_free (&dupmsr);
|
|
return NULL;
|
|
}
|
|
|
|
memcpy (dupmsr->datasamples, msr->datasamples, msr->datasize);
|
|
|
|
if (dupmsr->datasamples)
|
|
{
|
|
dupmsr->datasize = msr->datasize;
|
|
dupmsr->numsamples = msr->numsamples;
|
|
}
|
|
}
|
|
|
|
return dupmsr;
|
|
} /* End of msr3_duplicate() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Calculate time of the last sample in a record
|
|
*
|
|
* If leap seconds have been loaded into the internal library list:
|
|
* when a record completely contains a leap second, starts before and
|
|
* ends after, the calculated end time will be adjusted (reduced) by
|
|
* one second.
|
|
* @note On the epoch time scale the value of a leap second is the
|
|
* same as the second following the leap second, without external
|
|
* information the values are ambiguous.
|
|
* \sa ms_readleapsecondfile()
|
|
*
|
|
* @param[in] msr ::MS3Record to calculate end time of
|
|
*
|
|
* @returns Time of the last sample on success and NSTERROR on error.
|
|
***************************************************************************/
|
|
nstime_t
|
|
msr3_endtime (const MS3Record *msr)
|
|
{
|
|
int64_t sampleoffset = 0;
|
|
|
|
if (!msr)
|
|
return NSTERROR;
|
|
|
|
if (msr->samplecnt > 0)
|
|
sampleoffset = msr->samplecnt - 1;
|
|
|
|
return ms_sampletime (msr->starttime, sampleoffset, msr->samprate);
|
|
} /* End of msr3_endtime() */
|
|
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Print header values of an MS3Record
|
|
*
|
|
* @param[in] msr ::MS3Record to print
|
|
* @param[in] details Flags to control the level of details:
|
|
* @parblock
|
|
* - \c 0 - print a single summary line
|
|
* - \c 1 - print most details of header
|
|
* - \c >1 - print all details of header and extra headers if present
|
|
* @endparblock
|
|
***************************************************************************/
|
|
void
|
|
msr3_print (const MS3Record *msr, int8_t details)
|
|
{
|
|
char time[40];
|
|
char b;
|
|
|
|
if (!msr)
|
|
return;
|
|
|
|
/* Generate a start time string */
|
|
ms_nstime2timestr (msr->starttime, time, ISOMONTHDAY_DOY_Z, NANO_MICRO);
|
|
|
|
/* Report information in the fixed header */
|
|
if (details > 0)
|
|
{
|
|
ms_log (0, "%s, version %d, %d bytes (format: %d)\n",
|
|
msr->sid, msr->pubversion, msr->reclen, msr->formatversion);
|
|
ms_log (0, " start time: %s\n", time);
|
|
ms_log (0, " number of samples: %" PRId64 "\n", msr->samplecnt);
|
|
ms_log (0, " sample rate (Hz): %.10g\n", msr3_sampratehz(msr));
|
|
|
|
if (details > 1)
|
|
{
|
|
b = msr->flags;
|
|
ms_log (0, " flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
|
|
bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
|
|
bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
|
|
if (b & 0x01)
|
|
ms_log (0, " [Bit 0] Calibration signals present\n");
|
|
if (b & 0x02)
|
|
ms_log (0, " [Bit 1] Time tag is questionable\n");
|
|
if (b & 0x04)
|
|
ms_log (0, " [Bit 2] Clock locked\n");
|
|
if (b & 0x08)
|
|
ms_log (0, " [Bit 3] Undefined bit set\n");
|
|
if (b & 0x10)
|
|
ms_log (0, " [Bit 4] Undefined bit set\n");
|
|
if (b & 0x20)
|
|
ms_log (0, " [Bit 5] Undefined bit set\n");
|
|
if (b & 0x40)
|
|
ms_log (0, " [Bit 6] Undefined bit set\n");
|
|
if (b & 0x80)
|
|
ms_log (0, " [Bit 7] Undefined bit set\n");
|
|
}
|
|
|
|
ms_log (0, " CRC: 0x%0X\n", msr->crc);
|
|
ms_log (0, " extra header length: %d bytes\n", msr->extralength);
|
|
ms_log (0, " data payload length: %d bytes\n", msr->datalength);
|
|
ms_log (0, " payload encoding: %s (val: %d)\n",
|
|
(char *)ms_encodingstr (msr->encoding), msr->encoding);
|
|
|
|
if (details > 1 && msr->extralength > 0 && msr->extra)
|
|
{
|
|
ms_log (0, " extra headers:\n");
|
|
mseh_print (msr, 16);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ms_log (0, "%s, %d, %d, %" PRId64 " samples, %-.10g Hz, %s\n",
|
|
msr->sid, msr->pubversion, msr->reclen,
|
|
msr->samplecnt, msr3_sampratehz(msr), time);
|
|
}
|
|
} /* End of msr3_print() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Resize data sample buffer of ::MS3Record to what is needed
|
|
*
|
|
* This routine should only be used if pre-allocation of memory, via
|
|
* ::libmseed_prealloc_block_size, was enabled to allocate the buffer.
|
|
*
|
|
* @param[in] msr ::MS3Record to resize buffer
|
|
*
|
|
* @returns Return 0 on success, otherwise returns a libmseed error code.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
int
|
|
msr3_resize_buffer (MS3Record *msr)
|
|
{
|
|
uint8_t samplesize = 0;
|
|
size_t datasize;
|
|
|
|
if (!msr)
|
|
{
|
|
ms_log (2, "Required argument not defined: 'msr'\n");
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
samplesize = ms_samplesize(msr->sampletype);
|
|
|
|
if (samplesize && msr->datasamples && msr->numsamples > 0)
|
|
{
|
|
datasize = (size_t) msr->numsamples * samplesize;
|
|
|
|
if (msr->datasize > datasize)
|
|
{
|
|
msr->datasamples = libmseed_memory.realloc (msr->datasamples, datasize);
|
|
|
|
if (msr->datasamples == NULL)
|
|
{
|
|
ms_log (2, "%s: Cannot (re)allocate memory\n", msr->sid);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
msr->datasize = datasize;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
} /* End of msr3_resize_buffer() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Calculate sample rate in samples/second (Hertz) for a given ::MS3Record
|
|
*
|
|
* @param[in] msr ::MS3Record to calculate sample rate for
|
|
*
|
|
* @returns Return sample rate in Hertz (samples per second)
|
|
***************************************************************************/
|
|
inline double
|
|
msr3_sampratehz (const MS3Record *msr)
|
|
{
|
|
if (!msr)
|
|
return 0.0;
|
|
|
|
if (msr->samprate < 0.0)
|
|
return (-1.0 / msr->samprate);
|
|
else
|
|
return msr->samprate;
|
|
} /* End of msr3_sampratehz() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Calculate data latency based on the host time
|
|
*
|
|
* Calculation is based on the time of the last sample in the record; in
|
|
* other words, the difference between the host time and the time of
|
|
* the last sample in the record.
|
|
*
|
|
* Double precision is returned, but the true precision is dependent
|
|
* on the accuracy of the host system clock among other things.
|
|
*
|
|
* @param[in] msr ::MS3Record to calculate lactency for
|
|
*
|
|
* @returns seconds of latency or 0.0 on error (indistinguishable from
|
|
* 0.0 latency).
|
|
***************************************************************************/
|
|
double
|
|
msr3_host_latency (const MS3Record *msr)
|
|
{
|
|
double span = 0.0; /* Time covered by the samples */
|
|
double epoch; /* Current epoch time */
|
|
double latency = 0.0;
|
|
time_t tv;
|
|
|
|
if (msr == NULL)
|
|
return 0.0;
|
|
|
|
/* Calculate the time covered by the samples */
|
|
if (msr->samprate > 0.0 && msr->samplecnt > 0)
|
|
span = (1.0 / msr->samprate) * (msr->samplecnt - 1);
|
|
|
|
/* Grab UTC time according to the system clock */
|
|
epoch = (double)time (&tv);
|
|
|
|
/* Now calculate the latency */
|
|
latency = epoch - ((double)msr->starttime / NSTMODULUS) - span;
|
|
|
|
return latency;
|
|
} /* End of msr3_host_latency() */
|