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
4089int 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