forked from labstreaminglayer/App-LabRecorder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxdfwriter.h
158 lines (140 loc) · 5.07 KB
/
xdfwriter.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#pragma once
#include "conversions.h"
#include <cassert>
#include <mutex>
#include <sstream>
#include <thread>
#include <type_traits>
#include <vector>
#ifdef XDFZ_SUPPORT
#include <boost/iostreams/filtering_stream.hpp>
using outfile_t = boost::iostreams::filtering_ostream;
#else
#include <fstream>
using outfile_t = std::ofstream;
#endif
using streamid_t = uint32_t;
// the currently defined chunk tags
enum class chunk_tag_t : uint16_t {
fileheader = 1, // FileHeader chunk
streamheader = 2, // StreamHeader chunk
samples = 3, // Samples chunk
clockoffset = 4, // ClockOffset chunk
boundary = 5, // Boundary chunk
streamfooter = 6, // StreamFooter chunk
undefined = 0
};
class XDFWriter {
private:
outfile_t file_;
void _write_chunk_header(
chunk_tag_t tag, std::size_t length, const streamid_t *streamid_p = nullptr);
std::mutex write_mut;
// write a generic chunk
void _write_chunk(
chunk_tag_t tag, const std::string &content, const streamid_t *streamid_p = nullptr);
public:
/**
* @brief XDFWriter Construct a XDFWriter object
* @param filename Filename to write to
*/
XDFWriter(const std::string &filename);
template <typename T>
void write_data_chunk(streamid_t streamid, const std::vector<double> ×tamps,
const T *chunk, uint32_t n_samples, uint32_t n_channels);
template <typename T>
void write_data_chunk(streamid_t streamid, const std::vector<double> ×tamps,
const std::vector<T> &chunk, uint32_t n_channels) {
assert(timestamps.size() * n_channels == chunk.size());
write_data_chunk(streamid, timestamps, chunk.data(), timestamps.size(), n_channels);
}
template <typename T>
void write_data_chunk_nested(streamid_t streamid, const std::vector<double> ×tamps,
const std::vector<std::vector<T>> &chunk);
/**
* @brief write_stream_header Write the stream header, see also
* @see https://github.com/sccn/xdf/wiki/Specifications#clockoffset-chunk
* @param streamid Numeric stream identifier
* @param content XML-formatted stream header
*/
void write_stream_header(streamid_t streamid, const std::string &content);
/**
* @brief write_stream_footer
* @see https://github.com/sccn/xdf/wiki/Specifications#streamfooter-chunk
*/
void write_stream_footer(streamid_t streamid, const std::string &content);
/**
* @brief write_stream_offset Record the time discrepancy between the
* streaming and the recording PC
* @see https://github.com/sccn/xdf/wiki/Specifications#clockoffset-chunk
*/
void write_stream_offset(streamid_t streamid, double collectiontime, double offset);
/**
* @brief write_boundary_chunk Insert a boundary chunk that's mostly used
* to recover from errors in XDF files by providing a restart marker.
*/
void write_boundary_chunk();
};
inline void write_ts(std::ostream &out, double ts) {
// write timestamp
if (ts == 0)
out.put(0);
else {
// [TimeStampBytes]
out.put(8);
// [TimeStamp]
write_little_endian(out, ts);
}
}
template <typename T>
void XDFWriter::write_data_chunk(streamid_t streamid, const std::vector<double> ×tamps,
const T *chunk, uint32_t n_samples, uint32_t n_channels) {
/**
Samples data chunk: [Tag 3] [VLA ChunkLen] [StreamID] [VLA NumSamples]
[NumSamples x [VLA TimestampLen] [TimeStampLen]
[NumSamples x NumChannels Sample]
*/
if (n_samples == 0) return;
if (timestamps.size() != n_samples)
throw std::runtime_error("timestamp / sample count mismatch");
// generate [Samples] chunk contents...
std::ostringstream out;
write_fixlen_int(out, 0x0FFFFFFF); // Placeholder length, will be replaced later
for (double ts : timestamps) {
write_ts(out, ts);
// write sample, get the current position in the chunk array back
chunk = write_sample_values(out, chunk, n_channels);
}
std::string outstr(out.str());
// Replace length placeholder
auto s = static_cast<uint32_t>(n_samples);
std::copy(reinterpret_cast<char *>(&s), reinterpret_cast<char *>(&s + 1), outstr.begin() + 1);
std::lock_guard<std::mutex> lock(write_mut);
_write_chunk(chunk_tag_t::samples, outstr, &streamid);
}
template <typename T>
void XDFWriter::write_data_chunk_nested(streamid_t streamid, const std::vector<double> ×tamps,
const std::vector<std::vector<T>> &chunk) {
if (chunk.size() == 0) return;
auto n_samples = timestamps.size();
if (timestamps.size() != chunk.size())
throw std::runtime_error("timestamp / sample count mismatch");
auto n_channels = chunk[0].size();
// generate [Samples] chunk contents...
std::ostringstream out;
write_fixlen_int(out, 0x0FFFFFFF); // Placeholder length, will be replaced later
auto sample_it = chunk.cbegin();
for (double ts : timestamps) {
assert(n_channels == sample_it->size());
write_ts(out, ts);
// write sample, get the current position in the chunk array back
write_sample_values(out, sample_it->data(), n_channels);
sample_it++;
}
std::string outstr(out.str());
// Replace length placeholder
auto s = static_cast<uint32_t>(n_samples);
std::copy(reinterpret_cast<char *>(&s), reinterpret_cast<char *>(&s + 1), outstr.begin() + 1);
std::lock_guard<std::mutex> lock(write_mut);
_write_chunk(chunk_tag_t::samples, outstr, &streamid);
}