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_pSocket(nullptr)
69 , m_bIsOpen(false)
70 , m_iChannelCount(info.channel_count())
71 , m_iBytesPerSample(static_cast<int>(info.channel_count() * sizeof(float)))
72 {
73 }
74
79
80 //=========================================================================================================
85 {
86 if (m_bIsOpen) {
87 return;
88 }
89
90 // Create socket (no parent, we manage lifetime ourselves)
91 m_pSocket = new QTcpSocket();
92
93 QString host = QString::fromStdString(m_info.data_host());
94 quint16 port = static_cast<quint16>(m_info.data_port());
95
96 if (host.isEmpty() || port == 0) {
97 delete m_pSocket;
98 m_pSocket = nullptr;
99 throw std::runtime_error("[lsl::stream_inlet] Invalid data host or port in stream_info");
100 }
101
102 m_pSocket->connectToHost(host, port);
103
104 if (!m_pSocket->waitForConnected(5000)) {
105 QString err = m_pSocket->errorString();
106 delete m_pSocket;
107 m_pSocket = nullptr;
108 throw std::runtime_error(std::string("[lsl::stream_inlet] Failed to connect to outlet: ")
109 + err.toStdString());
110 }
111
112 // Read the handshake header: "LSL1" (4 bytes) + channel_count (4 bytes, little-endian)
113 if (!m_pSocket->waitForReadyRead(5000)) {
114 delete m_pSocket;
115 m_pSocket = nullptr;
116 throw std::runtime_error("[lsl::stream_inlet] Timeout waiting for handshake from outlet");
117 }
118
119 QByteArray header;
120 while (header.size() < 8) {
121 if (m_pSocket->bytesAvailable() == 0) {
122 if (!m_pSocket->waitForReadyRead(5000)) {
123 break;
124 }
125 }
126 header.append(m_pSocket->read(8 - header.size()));
127 }
128
129 if (header.size() < 8 || header.left(4) != QByteArray("LSL1", 4)) {
130 delete m_pSocket;
131 m_pSocket = nullptr;
132 throw std::runtime_error("[lsl::stream_inlet] Invalid handshake from outlet");
133 }
134
135 // Read channel count from header (little-endian int32)
136 int headerChannels = 0;
137 std::memcpy(&headerChannels, header.constData() + 4, sizeof(int));
138 if (headerChannels != m_iChannelCount) {
139 qDebug() << "[lsl::stream_inlet] Warning: outlet reports" << headerChannels
140 << "channels, expected" << m_iChannelCount << "- using outlet value";
141 m_iChannelCount = headerChannels;
142 m_iBytesPerSample = m_iChannelCount * static_cast<int>(sizeof(float));
143 }
144
145 m_bIsOpen = true;
146 }
147
148 //=========================================================================================================
153 {
154 if (m_pSocket) {
155 if (m_pSocket->state() == QAbstractSocket::ConnectedState) {
156 m_pSocket->disconnectFromHost();
157 if (m_pSocket->state() != QAbstractSocket::UnconnectedState) {
158 m_pSocket->waitForDisconnected(1000);
159 }
160 }
161 delete m_pSocket;
162 m_pSocket = nullptr;
163 }
164 m_bIsOpen = false;
165 m_rawBuffer.clear();
166 }
167
168 //=========================================================================================================
175 {
176 if (!m_bIsOpen || !m_pSocket) {
177 return false;
178 }
179
180 // Non-blocking check for available data
181 if (m_pSocket->bytesAvailable() > 0 || m_pSocket->waitForReadyRead(0)) {
182 QByteArray data = m_pSocket->readAll();
183 m_rawBuffer.append(data);
184 }
185
186 // A complete sample requires m_iBytesPerSample bytes
187 return (m_iBytesPerSample > 0) && (m_rawBuffer.size() >= m_iBytesPerSample);
188 }
189
190 //=========================================================================================================
196 std::vector<std::vector<float>> pullChunkFloat()
197 {
198 std::vector<std::vector<float>> chunk;
199
200 if (!m_bIsOpen || !m_pSocket) {
201 return chunk;
202 }
203
204 // Read any pending data
205 readPending();
206
207 if (m_iBytesPerSample <= 0) {
208 return chunk;
209 }
210
211 // Extract complete samples from the buffer
212 int nCompleteSamples = m_rawBuffer.size() / m_iBytesPerSample;
213 if (nCompleteSamples == 0) {
214 return chunk;
215 }
216
217 chunk.reserve(nCompleteSamples);
218 const char* ptr = m_rawBuffer.constData();
219
220 for (int s = 0; s < nCompleteSamples; ++s) {
221 std::vector<float> sample(m_iChannelCount);
222 std::memcpy(sample.data(), ptr + s * m_iBytesPerSample, m_iBytesPerSample);
223 chunk.push_back(std::move(sample));
224 }
225
226 // Remove consumed bytes from the buffer
227 int consumedBytes = nCompleteSamples * m_iBytesPerSample;
228 m_rawBuffer.remove(0, consumedBytes);
229
230 return chunk;
231 }
232
234 QTcpSocket* m_pSocket;
238 QByteArray m_rawBuffer;
239};
240
241//=============================================================================================================
242// DEFINE MEMBER METHODS
243//=============================================================================================================
244
246: m_pImpl(new StreamInletPrivate(info))
247{
248}
249
250//=============================================================================================================
251
253{
254 // unique_ptr destructor handles cleanup via StreamInletPrivate destructor
255}
256
257//=============================================================================================================
258
260{
261 m_pImpl->openStream();
262}
263
264//=============================================================================================================
265
267{
268 m_pImpl->closeStream();
269}
270
271//=============================================================================================================
272
274{
275 return m_pImpl->readPending();
276}
277
278//=============================================================================================================
279
280std::vector<std::vector<float>> stream_inlet::pull_chunk_float()
281{
282 return m_pImpl->pullChunkFloat();
283}
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()
stream_inlet(const stream_info &info)
std::vector< std::vector< float > > pull_chunk_float()