sccn / lsl_archived

Multi-modal time-synched data transmission over local network
242 stars 134 forks source link

Constant chunk size Matlab #143

Open aaschmidt opened 8 years ago

aaschmidt commented 8 years ago

I send a stream of data from a soundcard pushed with constant chunk size of 1024 samples (sampling frequency 48000Hz). The receiver then pulls the chunks, however in rare cases the chunk's size differs from 1024.

inlet = lsl_inlet(result{1}); while true [chunk,stamps] = inlet.pull_chunk(); pause(0.02) % to simulate data processing end

Does anyone can help me on this issue and propose a solution to receive reliable 1024 chunk sizes? Thanks in advance!

cboulay commented 8 years ago

48000 samples/sec / 1024 samples/chunk = 46.875 chunks / sec. That corresponds to 21.333 msec per chunk. You tell your script to pause for 20 msec after receiving every chunk. I'm not sure what timing APIs Matlab uses, or if you're using Windows, but there's a good chance that Matlab's pause function doesn't have microsecond resolution, so you might actually be pausing for more than 20 msec sometimes. Then you have network overhead and the time it takes to actually copy the data. Inevitably, there are going to be times where your signal processing takes more than 21.333 msec to complete an iteration. When that happens, the outlet will have pushed 2 chunks by the time you finally call pull_chunk again.

You have at least 2 non-exclusive solutions:

  1. Make your signal processing so fast that you are guaranteed to never have more than 1024 samples waiting for you.
  2. Make your signal processing be able to handle having more than 1024 samples in waiting.
    • This is a good idea anyway, because sometimes you'll get unexpected hitches in the OS.
    • This might be as simple as creating an inner loop in your signal processing methods to break the pulled chunk into sub-chunks of 1024 samples and processing those, but this can be dangerous if your signal processing is slow.

By the way, do you really need to process your data at 47 Hz? This isn't especially fast, but it's an odd target. If you're trying to give visual feedback then Matlab is probably not the best choice; Matlab graphing is really slow and I doubt it can go anywhere near 47 Hz.

aaschmidt commented 8 years ago

Thanks for the reply. It gave me some more insight.

I did some further testing right now: if I now guarantee that my signal processing chain is strictly faster than those 21.33 msec (at the moment I just perform a simple fft of the incoming data, which is much faster). Then, I observe that the maximal block size is indeed 1024. However, a lot of blocks that arrive are smaller. The attached plot tries to illustrate this: if I measure the size of all non-empty incoming chunks and plot it, it's far away from being constant.

Is there a way to circumvent this then and getting empty chunks up to the point 1024 samples are available. Otherwise I have to use some kind of internal buffer...

Thanks in advance!

size_chunks

cboulay commented 8 years ago

That's surprising to me because the outlet shouldn't be sending in less than 1024 at a time. But I don't know LSL under-the-hood very well, so maybe this isn't unexpected.

@dmedine , @chkothe , Is it expected that a stream that is being fed data in chunks of 1024 samples at a time could make fewer than 1024 samples available to an inlet? Is push_chunk really just a wrapper that loops around push_sample?

@aaschmidt, do you have the source code for the Outlet that's sending the data from the soundcard? Is it a C program, a C++ program, Matlab, Python, or something else? I'm just wondering if it is pushing chunks correctly.

What I would normally say to do is parameterize the pull_chunk function call with the number of samples you want (1024) and a non-zero timeout parameter, then pull_chunk will wait until the 1024 samples are available before it returns. See here for a description of the arguments that can be passed to pull_chunk.

However, it seems that the Matlab wrapper doesn't expose these arguments. The call to the mex function is here, and inside the mex function timeout is hard-coded to 0.

In case you're also into Python, it might be simplest to switch over to using that because its wrapper does expose the timeout argument.

You can also try to modify the mex function to expose these arguments. I'm not very good with Matlab mex files, so I don't know where to start there.

aaschmidt commented 8 years ago

Thanks for your reply.

The outlet is also written in matlab: it streams data with playrec from an external soundcard and then pushing it into the stream. I already checked, blocksize here is constant 1024.

I modified the mex function for pull_chunk (the corresponding code is lsl_pull_chunk_d.c). Indeed, the timeout parameter was hard-coded to 0.0. I changed it to a high value (like LSL_FOEVER in the C implementations) and recompiled mex. However, what I observed then was that I do not receive anything. It seems as if pull_chunk is waiting ...

I am still a bit confused, but keep working on it. However, if anyone of you has an idea, it is highly appreciated :)

cboulay commented 8 years ago

Is push_chunk really just a wrapper that loops around push_sample?

I tried to answer my own question.

  1. Matlab calls lsl_push_chunk
  2. lsl_push_chunk.c loops over each sample but only sets pushthrough on the last sample.

This is no different to what happens in Python or C. For example, in Python...

  1. lsl_push_chunk_dtp is called (do_push_chunk is just lsl_push_chunk_<datatype>).
  2. lsl_push_chunk_dtp calls push_chunk_multiplexed.
  3. push_chunk_multiplexed loops over the samples, calling push_sample on each, but only the last sample has pushthrough set to true.
  4. push_sample calls enqueue

I don't really know where enqueue comes from so that's where the trail runs cold for me. The fact that there's a pushthrough argument suggests to me that the samples aren't pushed through the network until the last sample in a chunk is sent. But I don't know. If this were true then you wouldn't ever have fewer than 1024 samples available in your stream.

cboulay commented 8 years ago

You could try using lsl_samples_available and call that until 1024 are available. But you shouldn't have to do that.

Can you verify that your audio outlet is setting the chunksize during its creation? If this is set, then the inlet should take that value as its max_chunklen by default and any pull_chunk operation with a long timeout should return as soon as max_chunklen samples are available.

If you are specifying the chunksize on outlet creation, but your pulls are still blocking, then you can try specifying the max_chunklen explicitly. In Matlab this is called the ChunkSize.

dmedine commented 8 years ago

I believe that enqueue is a 'pattern' that is used frequently in boost::asio driven applications---which LSL certainly is. My understanding is that this call (ultimately) shoves the data into the socket which lives down in the OS somewhere where asio can deal with it accordingly (i.e. publish the sample on the network).

The function is defined on lines 174-178 of stream_outlet_impl.h.

This is indeed the same for push_sample, push_chunk and should be the final backend to any and all LSL wrappers such as liblsl-Matlab and liblsl-Python.

On 10/26/2016 5:22 AM, Chadwick Boulay wrote:

Is push_chunk really just a wrapper that loops around push_sample?
I tried to answer my own question.
  1. Matlab calls |lsl_push_chunk| https://github.com/sccn/labstreaminglayer/blob/master/LSL/liblsl-Matlab/lsl_outlet.m#L104
  2. lsl_push_chunk.c loops over each sample https://github.com/sccn/labstreaminglayer/blob/8d032fb43245be0d8598488d2cf783ac36a97831/LSL/liblsl-Matlab/mex/lsl_push_chunk.c#L66-L68 but only sets |pushthrough| on the last sample.

This is no different to what happens in Python or C. For example, in Python...

  1. |lsl_push_chunk_dtp| is called https://github.com/sccn/labstreaminglayer/blob/8d032fb43245be0d8598488d2cf783ac36a97831/LSL/liblsl-Python/pylsl/pylsl.py#L481-L484 (|do_push_chunk| is just |lsl_pushchunk|).
  2. |lsl_push_chunk_dtp| calls |push_chunk_multiplexed| https://github.com/sccn/labstreaminglayer/blob/8d032fb43245be0d8598488d2cf783ac36a97831/LSL/liblsl/src/lsl_outlet_c.cpp#L814-L831.
  3. |push_chunk_multiplexed| loops over the samples, calling |push_sample| on each https://github.com/sccn/labstreaminglayer/blob/8d032fb43245be0d8598488d2cf783ac36a97831/LSL/liblsl/src/stream_outlet_impl.h#L126-L140, but only the last sample has |pushthrough| set to true.
  4. |push_sample| calls |enqueue| https://github.com/sccn/labstreaminglayer/blob/8d032fb43245be0d8598488d2cf783ac36a97831/LSL/liblsl/src/stream_outlet_impl.h#L55

I don't really know where |enqueue| comes from so that's where the trail runs cold for me. The fact that there's a |pushthrough| argument suggests to me that the samples aren't pushed through the network until the last sample in a chunk is sent. But I don't know. If this were true then you wouldn't ever have fewer than 1024 samples available in your stream.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sccn/labstreaminglayer/issues/143#issuecomment-256331219, or mute the thread https://github.com/notifications/unsubscribe-auth/ADch7o2gQTTW82qBmFwBene8lNP376bVks5q30YcgaJpZM4KcFIt.

cboulay commented 8 years ago

Thanks @dmedine for pointing me to the correct enqueue. A few more tumbles down the rabbit hole... So the pushthrough property gets set on a sample_p. Then that sample gets passed as an argument to consumers_[]->push_sample(sample).

consumers_ is a consumer_set which is a consumer_queue.

The consumer_queue::push_sample also does not use pushthrough, but just asks a buffer_type buffer_ to push the sample.

buffer_type is a boost::lockfree::spsc_queue<sample_p> buffer_type;

I tried to read through the BOOST code a little but I still couldn't find where pushthrough was actually getting used, so I don't know if in fact it makes a difference. If it does not, then that would explain why you're seeing fewer than 1024 samples sometimes.