|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2009 by gempa GmbH *
|
|
|
|
* *
|
|
|
|
* All Rights Reserved. *
|
|
|
|
* *
|
|
|
|
* NOTICE: All information contained herein is, and remains *
|
|
|
|
* the property of gempa GmbH and its suppliers, if any. The intellectual *
|
|
|
|
* and technical concepts contained herein are proprietary to gempa GmbH *
|
|
|
|
* and its suppliers. *
|
|
|
|
* Dissemination of this information or reproduction of this material *
|
|
|
|
* is strictly forbidden unless prior written permission is obtained *
|
|
|
|
* from gempa GmbH. *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
#include <gempa/caps/rawpacket.h>
|
|
|
|
#include <gempa/caps/log.h>
|
|
|
|
#include <gempa/caps/riff.h>
|
|
|
|
#include <gempa/caps/utils.h>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
#define DT2STR(prefix, dt) \
|
|
|
|
do { \
|
|
|
|
switch ( dt ) { \
|
|
|
|
case DT_DOUBLE: \
|
|
|
|
return prefix"DOUBLE"; \
|
|
|
|
case DT_FLOAT: \
|
|
|
|
return prefix"FLOAT"; \
|
|
|
|
case DT_INT64: \
|
|
|
|
return prefix"INT64"; \
|
|
|
|
case DT_INT32: \
|
|
|
|
return prefix"INT32"; \
|
|
|
|
case DT_INT16: \
|
|
|
|
return prefix"INT16"; \
|
|
|
|
case DT_INT8: \
|
|
|
|
return prefix"INT8"; \
|
|
|
|
default: \
|
|
|
|
break; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
while (0); \
|
|
|
|
return prefix"UNKWN"; \
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace Gempa {
|
|
|
|
namespace CAPS {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
inline int sizeOf(DataType dt) {
|
|
|
|
switch ( dt ) {
|
|
|
|
case DT_DOUBLE:
|
|
|
|
return sizeof(double);
|
|
|
|
case DT_FLOAT:
|
|
|
|
return sizeof(float);
|
|
|
|
case DT_INT64:
|
|
|
|
return sizeof(int64_t);
|
|
|
|
case DT_INT32:
|
|
|
|
return sizeof(int32_t);
|
|
|
|
case DT_INT16:
|
|
|
|
return sizeof(int16_t);
|
|
|
|
case DT_INT8:
|
|
|
|
return sizeof(int8_t);
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline Time getEndTime(const Time &stime, size_t count, const DataRecord::Header &header) {
|
|
|
|
if ( header.samplingFrequencyNumerator == 0 ||
|
|
|
|
header.samplingFrequencyDenominator == 0 ) return stime;
|
|
|
|
|
|
|
|
return stime + samplesToTimeSpan(header, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
RawDataRecord::RawDataRecord() : _dataOfs(0), _dataSize(0), _dirty(false) {}
|
|
|
|
|
|
|
|
const char *RawDataRecord::formatName() const {
|
|
|
|
DT2STR("RAW/", _currentHeader.dataType)
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RawDataRecord::readMetaData(std::streambuf &buf, int size, Header &header,
|
|
|
|
Time &startTime, Time &endTime) {
|
|
|
|
if ( !header.get(buf) ) return false;
|
|
|
|
|
|
|
|
_currentHeader.dataType = header.dataType;
|
|
|
|
|
|
|
|
size -= header.dataSize();
|
|
|
|
int dtsize = sizeOf(header.dataType);
|
|
|
|
if ( dtsize == 0 ) {
|
|
|
|
CAPS_WARNING("unknown data type: %d", header.dataType);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int count = size / dtsize;
|
|
|
|
|
|
|
|
startTime = timestampToTime(header.samplingTime);
|
|
|
|
endTime = getEndTime(startTime, count, header);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RawDataRecord::setHeader(const Header &header) {
|
|
|
|
_header = header;
|
|
|
|
_currentHeader = _header;
|
|
|
|
_startTime = timestampToTime(_header.samplingTime);
|
|
|
|
_dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const DataRecord::Header *RawDataRecord::header() const {
|
|
|
|
return &_header;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RawDataRecord::setDataType(DataType dt) {
|
|
|
|
_header.dataType = dt;
|
|
|
|
_currentHeader = _header;
|
|
|
|
_dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RawDataRecord::setStartTime(const Time &time) {
|
|
|
|
_header.setSamplingTime(time);
|
|
|
|
_currentHeader = _header;
|
|
|
|
_startTime = time;
|
|
|
|
_dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RawDataRecord::setSamplingFrequency(uint16_t numerator, uint16_t denominator) {
|
|
|
|
_header.samplingFrequencyNumerator = numerator;
|
|
|
|
_header.samplingFrequencyDenominator = denominator;
|
|
|
|
_currentHeader = _header;
|
|
|
|
_dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Time RawDataRecord::startTime() const {
|
|
|
|
return _startTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
Time RawDataRecord::endTime() const {
|
|
|
|
if ( _dirty ) {
|
|
|
|
_endTime = getEndTime(_startTime, _data.size() / sizeOf(_header.dataType), _header);
|
|
|
|
_dirty = false;
|
|
|
|
}
|
|
|
|
return _endTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RawDataRecord::canTrim() const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RawDataRecord::canMerge() const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RawDataRecord::trim(const Time &start, const Time &end) const {
|
|
|
|
if ( _header.samplingFrequencyNumerator <= 0 || _header.samplingFrequencyDenominator <= 0 )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// If the start time is behind the end time return false
|
|
|
|
if ( start > end ) return false;
|
|
|
|
|
|
|
|
// Initialize original values
|
|
|
|
int dataTypeSize = sizeOf(_header.dataType);
|
|
|
|
_dataSize = _data.size();
|
|
|
|
if ( dataTypeSize == 0 ) {
|
|
|
|
CAPS_WARNING("unknown data type: %d", _header.dataType);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_startTime = timestampToTime(_header.samplingTime);
|
|
|
|
_endTime = _startTime + samplesToTimeSpan(_header, _dataSize / dataTypeSize);
|
|
|
|
_dirty = false;
|
|
|
|
|
|
|
|
// Trim front
|
|
|
|
if ( start > _startTime ) {
|
|
|
|
TimeSpan ofs = start - _startTime;
|
|
|
|
int sampleOfs = timeSpanToSamplesCeil(_header, ofs);
|
|
|
|
_dataOfs = sampleOfs * dataTypeSize;
|
|
|
|
if ( _dataSize > _dataOfs )
|
|
|
|
_dataSize -= _dataOfs;
|
|
|
|
else
|
|
|
|
_dataSize = 0;
|
|
|
|
_startTime += samplesToTimeSpan(_header, sampleOfs);
|
|
|
|
timeToTimestamp(_currentHeader.samplingTime, _startTime);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_currentHeader.samplingTime = _header.samplingTime;
|
|
|
|
_dataOfs = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trim back
|
|
|
|
if ( end.valid() && (end < _endTime) ) {
|
|
|
|
TimeSpan ofs = _endTime - end;
|
|
|
|
int sampleOfs = timeSpanToSamplesFloor(_header, ofs);
|
|
|
|
_dataSize -= sampleOfs * dataTypeSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t sampleCount = _dataSize / dataTypeSize;
|
|
|
|
_endTime = _startTime + samplesToTimeSpan(_currentHeader, sampleCount);
|
|
|
|
|
|
|
|
CAPS_DEBUG("trimmed: %s ~ %s: %d samples",
|
|
|
|
_startTime.iso().c_str(),
|
|
|
|
_endTime.iso().c_str(), (int)sampleCount);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t RawDataRecord::dataSize(bool withHeader) const {
|
|
|
|
if ( withHeader )
|
|
|
|
return _dataSize + _header.dataSize();
|
|
|
|
else
|
|
|
|
return _dataSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
DataRecord::ReadStatus RawDataRecord::get(streambuf &buf, int size,
|
|
|
|
const Time &start, const Time &end,
|
|
|
|
int maxBytes) {
|
|
|
|
size -= _header.dataSize();
|
|
|
|
if ( size < 0 ) return RS_Error;
|
|
|
|
if ( !_header.get(buf) ) {
|
|
|
|
CAPS_WARNING("invalid raw header");
|
|
|
|
return RS_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return getData(buf, size, start, end, maxBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
DataRecord::ReadStatus RawDataRecord::getData(streambuf &buf, int size,
|
|
|
|
const Time &start, const Time &end,
|
|
|
|
int maxBytes) {
|
|
|
|
bool partial = false;
|
|
|
|
int sampleOfs;
|
|
|
|
int sampleCount;
|
|
|
|
int dataTypeSize = sizeOf(_header.dataType);
|
|
|
|
if ( dataTypeSize == 0 ) {
|
|
|
|
CAPS_WARNING("unknown data type: %d", _header.dataType);
|
|
|
|
return RS_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
sampleCount = size / dataTypeSize;
|
|
|
|
|
|
|
|
_startTime = timestampToTime(_header.samplingTime);
|
|
|
|
|
|
|
|
if ( _header.samplingFrequencyDenominator > 0 &&
|
|
|
|
_header.samplingFrequencyNumerator > 0 ) {
|
|
|
|
_endTime = _startTime + samplesToTimeSpan(_header, sampleCount);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_endTime = Time();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (start.valid() || end.valid()) && _endTime.valid() ) {
|
|
|
|
// Out of bounds?
|
|
|
|
if ( end.valid() && (end <= _startTime) )
|
|
|
|
return RS_AfterTimeWindow;
|
|
|
|
|
|
|
|
if ( start.valid() && (start >= _endTime) )
|
|
|
|
return RS_BeforeTimeWindow;
|
|
|
|
|
|
|
|
// Trim packet front
|
|
|
|
if ( _startTime < start ) {
|
|
|
|
TimeSpan ofs = start - _startTime;
|
|
|
|
sampleOfs = timeSpanToSamplesFloor(_header, ofs);
|
|
|
|
sampleCount -= sampleOfs;
|
|
|
|
CAPS_DEBUG("Triming packet start: added offset of %d samples", sampleOfs);
|
|
|
|
_startTime += samplesToTimeSpan(_header, sampleOfs);
|
|
|
|
// Update header timespan
|
|
|
|
timeToTimestamp(_header.samplingTime, _startTime);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sampleOfs = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( maxBytes > 0 ) {
|
|
|
|
int maxSamples = maxBytes / dataTypeSize;
|
|
|
|
// Here we need to clip the complete dataset and only
|
|
|
|
// return a partial record
|
|
|
|
if ( maxSamples < sampleCount ) {
|
|
|
|
CAPS_DEBUG("Clip %d available samples to %d", sampleCount, maxSamples);
|
|
|
|
_endTime -= samplesToTimeSpan(_header, sampleCount-maxSamples);
|
|
|
|
sampleCount = maxSamples;
|
|
|
|
partial = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trim back
|
|
|
|
if ( end.valid() && (end < _endTime) ) {
|
|
|
|
TimeSpan ofs = _endTime - end;
|
|
|
|
int trimEnd = timeSpanToSamplesFloor(_header, ofs);
|
|
|
|
sampleCount -= trimEnd;
|
|
|
|
_endTime = _startTime + samplesToTimeSpan(_header, sampleCount);
|
|
|
|
CAPS_DEBUG("Triming packet end: added offset of %d samples", trimEnd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sampleOfs = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( sampleCount == 0 ) {
|
|
|
|
return RS_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentHeader = _header;
|
|
|
|
|
|
|
|
switch ( _header.dataType ) {
|
|
|
|
case DT_INT8:
|
|
|
|
{
|
|
|
|
// Stay with little endian data
|
|
|
|
RIFF::VectorChunk<1,false> dataChunk(_data, sampleOfs, sampleCount);
|
|
|
|
if ( !dataChunk.get(buf, size) ) return RS_Error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DT_INT16:
|
|
|
|
{
|
|
|
|
// Stay with little endian data
|
|
|
|
RIFF::VectorChunk<2,false> dataChunk(_data, sampleOfs, sampleCount);
|
|
|
|
if ( !dataChunk.get(buf, size) ) return RS_Error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DT_INT32:
|
|
|
|
case DT_FLOAT:
|
|
|
|
{
|
|
|
|
// Stay with little endian data
|
|
|
|
RIFF::VectorChunk<4,false> dataChunk(_data, sampleOfs, sampleCount);
|
|
|
|
if ( !dataChunk.get(buf, size) ) return RS_Error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DT_INT64:
|
|
|
|
case DT_DOUBLE:
|
|
|
|
{
|
|
|
|
// Stay with little endian data
|
|
|
|
RIFF::VectorChunk<8,false> dataChunk(_data, sampleOfs, sampleCount);
|
|
|
|
if ( !dataChunk.get(buf, size) ) return RS_Error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
CAPS_ERROR("THIS SHOULD NEVER HAPPEN: ignored invalid data with type %d",
|
|
|
|
_header.dataType);
|
|
|
|
return RS_Error;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Initialize indicies
|
|
|
|
_dataOfs = 0;
|
|
|
|
_dataSize = _data.size();
|
|
|
|
|
|
|
|
return partial?RS_Partial:RS_Complete;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool RawDataRecord::put(std::streambuf &buf, bool withHeader) const {
|
|
|
|
if ( withHeader && !_currentHeader.put(buf) ) return false;
|
|
|
|
|
|
|
|
switch ( _header.dataType ) {
|
|
|
|
case DT_INT8:
|
|
|
|
{
|
|
|
|
// Data is already in little endian
|
|
|
|
RIFF::CPtrChunk<1,false> dataChunk(&_data[_dataOfs], _dataSize);
|
|
|
|
if ( !dataChunk.put(buf) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DT_INT16:
|
|
|
|
{
|
|
|
|
// Data is already in little endian
|
|
|
|
RIFF::CPtrChunk<2,false> dataChunk(&_data[_dataOfs], _dataSize);
|
|
|
|
if ( !dataChunk.put(buf) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DT_INT32:
|
|
|
|
case DT_FLOAT:
|
|
|
|
{
|
|
|
|
// Data is already in little endian
|
|
|
|
RIFF::CPtrChunk<4,false> dataChunk(&_data[_dataOfs], _dataSize);
|
|
|
|
if ( !dataChunk.put(buf) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DT_INT64:
|
|
|
|
case DT_DOUBLE:
|
|
|
|
{
|
|
|
|
// Data is already in little endian
|
|
|
|
RIFF::CPtrChunk<8,false> dataChunk(&_data[_dataOfs], _dataSize);
|
|
|
|
if ( !dataChunk.put(buf) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
CAPS_ERROR("THIS SHOULD NEVER HAPPEN: ignored invalid data with type %d",
|
|
|
|
_header.dataType);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RawDataRecord::setBuffer(const void *data, size_t size) {
|
|
|
|
_data.resize(size);
|
|
|
|
_dataSize = size;
|
|
|
|
_dataOfs = 0;
|
|
|
|
memcpy(_data.data(), data, size);
|
|
|
|
_dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *FixedRawDataRecord::formatName() const {
|
|
|
|
DT2STR("FIXEDRAW/", _currentHeader.dataType)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|