v2.0.0
Loading...
Searching...
No Matches
lsl_stream_inlet.cpp
Go to the documentation of this file.
1//=============================================================================================================
27
28//=============================================================================================================
29// INCLUDES
30//=============================================================================================================
31
32#include "lsl_stream_inlet.h"
33
34//=============================================================================================================
35// QT INCLUDES
36//=============================================================================================================
37
38#include <QTcpSocket>
39#include <QByteArray>
40#include <QDebug>
41
42//=============================================================================================================
43// STL INCLUDES
44//=============================================================================================================
45
46#include <cstring>
47#include <stdexcept>
48
49//=============================================================================================================
50// USED NAMESPACES
51//=============================================================================================================
52
53using namespace LSLLIB;
54
55//=============================================================================================================
56// PRIVATE IMPLEMENTATION
57//=============================================================================================================
58
64{
65public:
67 : m_info(info)
68 , m_bIsOpen(false)
69 , m_iChannelCount(info.channel_count())
70 , m_iBytesPerSample(static_cast<int>(info.channel_count() * sizeof(float)))
71 {
72 }
73
78
79 //=========================================================================================================
84 {
85 if (m_bIsOpen) {
86 return;
87 }
88
89 // Create socket (no parent, we manage lifetime ourselves)
90 m_pSocket = std::make_unique<QTcpSocket>();
91
92 QString host = QString::fromStdString(m_info.data_host());
93 quint16 port = static_cast<quint16>(m_info.data_port());
94
95 if (host.isEmpty() || port == 0) {
96 m_pSocket.reset();
97 throw std::runtime_error("[lsl::stream_inlet] Invalid data host or port in stream_info");
98 }
99
100 m_pSocket->connectToHost(host, port);
101
102 if (!m_pSocket->waitForConnected(5000)) {
103 QString err = m_pSocket->errorString();
104 m_pSocket.reset();
105 throw std::runtime_error(std::string("[lsl::stream_inlet] Failed to connect to outlet: ") + err.toStdString());
106 }
107
108 // Read the handshake header: "LSL1" (4 bytes) + channel_count (4 bytes, little-endian)
109 if (!m_pSocket->waitForReadyRead(5000)) {
110 m_pSocket.reset();
111 throw std::runtime_error("[lsl::stream_inlet] Timeout waiting for handshake from outlet");
112 }
113
114 QByteArray header;
115 while (header.size() < 8) {
116 if (m_pSocket->bytesAvailable() == 0) {
117 if (!m_pSocket->waitForReadyRead(5000)) {
118 break;
119 }
120 }
121 header.append(m_pSocket->read(8 - header.size()));
122 }
123
124 if (header.size() < 8 || header.left(4) != QByteArray("LSL1", 4)) {
125 m_pSocket.reset();
126 throw std::runtime_error("[lsl::stream_inlet] Invalid handshake from outlet");
127 }
128
129 // Read channel count from header (little-endian int32)
130 int headerChannels = 0;
131 std::memcpy(&headerChannels, header.constData() + 4, sizeof(int));
132 if (headerChannels != m_iChannelCount) {
133 qDebug() << "[lsl::stream_inlet] Warning: outlet reports" << headerChannels
134 << "channels, expected" << m_iChannelCount << "- using outlet value";
135 m_iChannelCount = headerChannels;
136 m_iBytesPerSample = m_iChannelCount * static_cast<int>(sizeof(float));
137 }
138
139 m_bIsOpen = true;
140 }
141
142 //=========================================================================================================
147 {
148 if (m_pSocket) {
149 if (m_pSocket->state() == QAbstractSocket::ConnectedState) {
150 m_pSocket->disconnectFromHost();
151 if (m_pSocket->state() != QAbstractSocket::UnconnectedState) {
152 m_pSocket->waitForDisconnected(1000);
153 }
154 }
155 m_pSocket.reset();
156 }
157 m_bIsOpen = false;
158 m_rawBuffer.clear();
159 }
160
161 //=========================================================================================================
168 {
169 if (!m_bIsOpen || !m_pSocket) {
170 return false;
171 }
172
173 // Non-blocking check for available data
174 if (m_pSocket->bytesAvailable() > 0 || m_pSocket->waitForReadyRead(0)) {
175 QByteArray data = m_pSocket->readAll();
176 m_rawBuffer.append(data);
177 }
178
179 // A complete sample requires m_iBytesPerSample bytes
180 return (m_iBytesPerSample > 0) && (m_rawBuffer.size() >= m_iBytesPerSample);
181 }
182
183 //=========================================================================================================
189 std::vector<std::vector<float>> pullChunkFloat()
190 {
191 std::vector<std::vector<float>> chunk;
192
193 if (!m_bIsOpen || !m_pSocket) {
194 return chunk;
195 }
196
197 // Read any pending data
198 readPending();
199
200 if (m_iBytesPerSample <= 0) {
201 return chunk;
202 }
203
204 // Extract complete samples from the buffer
205 int nCompleteSamples = m_rawBuffer.size() / m_iBytesPerSample;
206 if (nCompleteSamples == 0) {
207 return chunk;
208 }
209
210 chunk.reserve(nCompleteSamples);
211 const char* ptr = m_rawBuffer.constData();
212
213 for (int s = 0; s < nCompleteSamples; ++s) {
214 std::vector<float> sample(m_iChannelCount);
215 std::memcpy(sample.data(), ptr + s * m_iBytesPerSample, m_iBytesPerSample);
216 chunk.push_back(std::move(sample));
217 }
218
219 // Remove consumed bytes from the buffer
220 int consumedBytes = nCompleteSamples * m_iBytesPerSample;
221 m_rawBuffer.remove(0, consumedBytes);
222
223 return chunk;
224 }
225
227 std::unique_ptr<QTcpSocket> m_pSocket;
231 QByteArray m_rawBuffer;
232};
233
234//=============================================================================================================
235// DEFINE MEMBER METHODS
236//=============================================================================================================
237
239: m_pImpl(std::make_unique<StreamInletPrivate>(info))
240{
241}
242
243//=============================================================================================================
244
246{
247 // unique_ptr destructor handles cleanup via StreamInletPrivate destructor
248}
249
250//=============================================================================================================
251
253{
254 m_pImpl->openStream();
255}
256
257//=============================================================================================================
258
260{
261 m_pImpl->closeStream();
262}
263
264//=============================================================================================================
265
267{
268 return m_pImpl->readPending();
269}
270
271//=============================================================================================================
272
273std::vector<std::vector<float>> stream_inlet::pull_chunk_float()
274{
275 return m_pImpl->pullChunkFloat();
276}
Declares stream_inlet, the client side of an LSL connection that pulls multichannel sample chunks fro...
Lab Streaming Layer (LSL) integration for real-time data exchange.
Value-type descriptor of a single LSL stream: semantic metadata plus transport endpoint,...
StreamInletPrivate(const stream_info &info)
std::vector< std::vector< float > > pullChunkFloat()
std::unique_ptr< QTcpSocket > m_pSocket
stream_inlet(const stream_info &info)
std::vector< std::vector< float > > pull_chunk_float()