Skip to content

Commit ee36a94

Browse files
committed
Further modified Send/Receive DataInChunks examples for better speed profiling.
1 parent b708b78 commit ee36a94

File tree

2 files changed

+101
-40
lines changed

2 files changed

+101
-40
lines changed

examples/ReceiveDataInChunks.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
#include <chrono>
12
#include <iostream>
23
#include <lsl_cpp.h>
34
#include <stdint.h>
5+
#include <thread>
46

57

68
int main(int argc, char **argv) {
79
std::cout << "ReceiveDataInChunks" << std::endl;
8-
std::cout << "ReceiveDataInChunks StreamName max_buflen" << std::endl;
10+
std::cout << "ReceiveDataInChunks StreamName max_buflen flush" << std::endl;
911

1012
try {
1113

1214
std::string name{argc > 1 ? argv[1] : "MyAudioStream"};
1315
int32_t max_buflen = argc > 2 ? std::stol(argv[2]) : 360;
16+
bool flush = argc > 3;
1417
// resolve the stream of interest & make an inlet
1518
lsl::stream_inlet inlet(lsl::resolve_stream("name", name).at(0), max_buflen);
1619

@@ -21,26 +24,32 @@ int main(int argc, char **argv) {
2124

2225
// and retrieve the chunks
2326
uint64_t k = 0, num_samples = 0;
27+
std::vector<std::vector<int16_t>> result;
28+
auto fetch_interval = std::chrono::milliseconds(20);
29+
auto next_fetch = std::chrono::steady_clock::now() + fetch_interval;
30+
31+
2432
while (true) {
25-
std::vector < std::vector<int16_t> > result;
26-
if (double timestamp = inlet.pull_chunk(result))
27-
num_samples += result.size();
33+
std::this_thread::sleep_until(next_fetch);
34+
if (flush) {
35+
// You almost certainly don't want to use flush. This is here so we
36+
// can test maximum outlet throughput.
37+
num_samples += inlet.flush();
38+
} else {
39+
if (double timestamp = inlet.pull_chunk(result)) num_samples += result.size();
40+
}
2841
k++;
29-
30-
// display code
42+
next_fetch += fetch_interval;
3143
if (k % 50 == 0) {
3244
double now = lsl::local_clock();
33-
if (now > next_display) {
34-
std::cout << num_samples / (now - starttime) << " samples/sec" << std::endl;
35-
next_display = now + 1;
36-
}
37-
if (now > next_reset) { std::cout << "Resetting counters..." << std::endl;
45+
std::cout << num_samples / (now - starttime) << " samples/sec" << std::endl;
46+
if (now > next_reset) {
47+
std::cout << "Resetting counters..." << std::endl;
3848
starttime = now;
3949
next_reset = now + 10;
4050
num_samples = 0;
4151
}
4252
}
43-
4453
}
4554

4655
} catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; }

examples/SendDataInChunks.cpp

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include <thread>
66
#include <algorithm>
77
#include <random>
8+
#ifndef M_PI
9+
#define M_PI 3.14159265358979323846
10+
#endif
811

912

1013
// define a packed sample struct (here: a 16 bit stereo sample).
@@ -13,29 +16,75 @@ struct stereo_sample {
1316
int16_t l, r;
1417
};
1518

16-
// fill buffer with data from device -- Normally your device SDK would provide such a function. Here we use a random number generator.
17-
void get_data_from_device(std::vector<std::vector<int16_t>> buffer, uint64_t &sample_counter) {
18-
static std::uniform_int_distribution<int16_t> distribution(
19-
std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max());
20-
static std::default_random_engine generator;
21-
22-
if (buffer[0].size() == 2) {
23-
// If there are only 2 channels then we'll do a sine wave, pretending this is an audio device.
24-
for (auto &frame : buffer) {
25-
frame[0] = static_cast<int16_t>(100 * sin(sample_counter / 200.));
26-
frame[1] = static_cast<int16_t>(120 * sin(sample_counter / 400.));
27-
sample_counter++;
19+
struct fake_device {
20+
/*
21+
We create a fake device that will generate data. The inner details are not
22+
so important because typically it will be up to the real data source + SDK
23+
to provide a way to get data.
24+
*/
25+
std::size_t n_channels;
26+
double srate;
27+
int64_t pattern_samples;
28+
int64_t head;
29+
std::vector<int16_t> pattern;
30+
std::chrono::steady_clock::time_point last_time;
31+
32+
fake_device(const int16_t n_channels, const float srate)
33+
: n_channels(n_channels), srate(srate), head(0) {
34+
pattern_samples = (int64_t)(srate - 0.5) + 1; // truncate OK.
35+
36+
// Pre-allocate entire test pattern. The data _could_ be generated on the fly
37+
// for a much smaller memory hit, but we also use this example application
38+
// to test LSL Outlet performance so we want to reduce out-of-LSL CPU
39+
// utilization.
40+
int64_t magnitude = std::numeric_limits<int16_t>::max();
41+
int64_t offset_0 = magnitude / 2;
42+
int64_t offset_step = magnitude / n_channels;
43+
pattern.reserve(pattern_samples * n_channels);
44+
for (auto sample_ix = 0; sample_ix < pattern_samples; ++sample_ix) {
45+
for (auto chan_ix = 0; chan_ix < n_channels; ++chan_ix) {
46+
pattern.emplace_back(
47+
offset_0 + chan_ix * offset_step +
48+
magnitude * static_cast<int16_t>(sin(M_PI * chan_ix * sample_ix / n_channels)));
49+
}
2850
}
51+
last_time = std::chrono::high_resolution_clock::now();
52+
}
53+
54+
std::vector<int16_t> get_data() {
55+
auto now = std::chrono::steady_clock::now();
56+
auto elapsed_nano =
57+
std::chrono::duration_cast<std::chrono::nanoseconds>(now - last_time).count();
58+
std::size_t elapsed_samples = std::size_t(elapsed_nano * srate * 1e-9); // truncate OK.
59+
std::vector<int16_t> result;
60+
result.resize(elapsed_samples * n_channels);
61+
int64_t ret_samples = get_data(result);
62+
std::vector<int16_t> output(result.begin(), result.begin() + ret_samples);
63+
return output;
2964
}
30-
else {
31-
for (auto &frame : buffer) {
32-
for (std::size_t chan_idx = 0; chan_idx < frame.size(); ++chan_idx) {
33-
frame[chan_idx] = distribution(generator);
65+
66+
std::size_t get_data(std::vector<int16_t> &buffer) {
67+
auto now = std::chrono::steady_clock::now();
68+
auto elapsed_nano =
69+
std::chrono::duration_cast<std::chrono::nanoseconds>(now - last_time).count();
70+
int64_t elapsed_samples = std::size_t(elapsed_nano * srate * 1e-9); // truncate OK.
71+
elapsed_samples = std::min(elapsed_samples, (int64_t)buffer.size());
72+
if (false) {
73+
// The fastest but no patterns.
74+
memset(&buffer[0], 23, buffer.size() * sizeof buffer[0]);
75+
} else {
76+
std::size_t end_sample = head + elapsed_samples;
77+
std::size_t nowrap_samples = std::min(pattern_samples - head, elapsed_samples);
78+
memcpy(&buffer[0], &(pattern[head]), nowrap_samples);
79+
if (end_sample > pattern_samples) {
80+
memcpy(&buffer[nowrap_samples], &(pattern[0]), elapsed_samples - nowrap_samples);
3481
}
35-
sample_counter++;
3682
}
83+
head = (head + elapsed_samples) % pattern_samples;
84+
last_time += std::chrono::nanoseconds(int64_t(1e9 * elapsed_samples / srate));
85+
return elapsed_samples;
3786
}
38-
}
87+
};
3988

4089
int main(int argc, char **argv) {
4190
std::cout << "SendDataInChunks" << std::endl;
@@ -44,14 +93,15 @@ int main(int argc, char **argv) {
4493
std::cout << "- chunk_rate -- number of chunks pushed per second. For this example, make it a common factor of samplingrate and 1000." << std::endl;
4594

4695
std::string name{argc > 1 ? argv[1] : "MyAudioStream"}, type{argc > 2 ? argv[2] : "Audio"};
47-
int samplingrate = argc > 3 ? std::stol(argv[3]) : 44100;
48-
int n_channels = argc > 4 ? std::stol(argv[4]) : 2;
96+
int samplingrate = argc > 3 ? std::stol(argv[3]) : 44100; // Here we specify srate, but typically this would come from the device.
97+
int n_channels = argc > 4 ? std::stol(argv[4]) : 2; // Here we specify n_chans, but typically this would come from theh device.
4998
int32_t max_buffered = argc > 5 ? std::stol(argv[5]) : 360;
5099
int32_t chunk_rate = argc > 6 ? std::stol(argv[6]) : 10; // Chunks per second.
51100
int32_t chunk_samples = samplingrate > 0 ? std::max((samplingrate / chunk_rate), 1) : 100; // Samples per chunk.
52101
int32_t chunk_duration = 1000 / chunk_rate; // Milliseconds per chunk
53102

54103
try {
104+
// Prepare the LSL stream.
55105
lsl::stream_info info(name, type, n_channels, samplingrate, lsl::cf_int16);
56106
lsl::stream_outlet outlet(info, 0, max_buffered);
57107
lsl::xml_element desc = info.desc();
@@ -64,10 +114,12 @@ int main(int argc, char **argv) {
64114
chn.append_child_value("type", "EEG");
65115
}
66116

67-
// Prepare buffer to get data from 'device'
68-
std::vector<std::vector<int16_t>> chunk_buffer(
69-
chunk_samples,
70-
std::vector<int16_t>(n_channels));
117+
// Create a connection to our device.
118+
fake_device my_device(n_channels, (float)samplingrate);
119+
120+
// Prepare buffer to get data from 'device'.
121+
// The buffer should be largery than you think you need. Here we make it twice as large.
122+
std::vector<int16_t> chunk_buffer(2 * chunk_samples * n_channels);
71123

72124
std::cout << "Now sending data..." << std::endl;
73125

@@ -76,16 +128,16 @@ int main(int argc, char **argv) {
76128
auto nextsample = std::chrono::high_resolution_clock::now();
77129
uint64_t sample_counter = 0;
78130
for (unsigned c = 0;; c++) {
79-
80131
// wait a bit
81132
nextsample += std::chrono::milliseconds(chunk_duration);
82133
std::this_thread::sleep_until(nextsample);
83134

84135
// Get data from device
85-
get_data_from_device(chunk_buffer, sample_counter);
136+
std::size_t returned_samples = my_device.get_data(chunk_buffer);
86137

87-
// send it to the outlet
88-
outlet.push_chunk(chunk_buffer);
138+
// send it to the outlet. push_chunk_multiplexed is one of the more complicated approaches.
139+
// other push_chunk methods are easier but slightly slower.
140+
outlet.push_chunk_multiplexed(chunk_buffer.data(), returned_samples * n_channels, 0.0, true);
89141
}
90142

91143
} catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; }

0 commit comments

Comments
 (0)