St?phane Letz wrote:
>>>
>>> Thinking again of what differentiate poll/select versus blocking
>>> read/write (as it was implemented in our code): say we have a
>>> fragment of 100 frames and we want to run/process with a buffer size
>>> of 256 frames :
>>>
>>> Reasoning at input side "only" for now:
>>
>> Looking at "half" the question is inadequate, because while you're
>> blocking for read (for example), the write side can underflow. You
>> need to be able to process either read *or* write, when they become
>> available.
>>
>>>
>>> 1) poll/select would wake up the application as soon as a first 100
>>> frames fragment is available at input? Then the application can read
>>> 100 frames without blocking, then the application suspends, the
>>> second 100 frames fragment wakes up the application... read 100
>>> frames again, ... until we have at least 256 frames (so 3 fragments)
>>> then the 256 frames input buffer can be processed.... and so on
>>>
>>> 2) the application tries to read a full 256 frames buffer, the read
>>> returns as soon as enough frames are available in the drivers so,
>>> here also when 3 fragments have been "internally" received?
>>>
>>> So what are the fundamental difference here?
>>
>> For a non-duplex operation, there isn't much difference. (Although
>> you might occasionally have more or less than 100 frames available at
>> a time. So you have to check before you transfer the data, or use a
>> non-blocking read, in case 1, otherwise it can degenerate into case 2.
>>
>> -- Garrett
>>>
>
> Ok, now the duplex case: in our model we *require* that the same
> number of frames (here 256) is given to the Process part at each
> cycle. Thus we need to "wait" at least 3 fragments to start processing
> (in either poll or "blocking 256 frames" read). Obviously if the
> application waits in the "blocking 256 frames" read, the write side
> may underflow. So we need to prefill the output side one complete 256
> frames buffer (at least) *before* starting the Read/Process/Write loop
> to avoid that situation.
>
> But then if we do :
>
> - prefill the output side with one complete 256 frames buffer
>
> - enter Read/Process/Write loop
>
> we are supposed to be in a case where output always has a buffer
> available, that is for a given cycle, we can *safely* read the 256
> frames input buffer, Process our 256 frames buffer and then write it.
> If for some reason the Process takes too much time, then we have an
> Xrun, but this make sense.
>
> As I understand the poll/select model, the application has to read
> data and reconstruct itself the 256 frames input buffer (of write the
> 256 output buffer by "slices"). On the contrary the blocking 256
> frames Read/Process/Write model let the driver does this job, and just
> has to prepare (that is write) the first output buffer. But then the
> Read/Process/Write is simpler and avoid some uneeded kernel/userland
> switches.
>
> Does this make sense?
A few notes:
1) The prefill means that you have to incur additional output latency
that you wouldn't otherwise need, using this approach (But you have to
prefill at least two fragments to avoid an underrun anyway. But your
approach requires *3* fragments, so even more latency. :-(
2) You have to size your buffers (256 bytes in your example) such that
you can't have underruns or overruns. You wind up having to rely on a
(probably reasonable, but I'm not entirely sure universally true)
behavior that the framework will never fall more than 2 fragments ahead
or behind.
The poll model I gave you totally disconnects your application's natural
processing chunk size (say 256 bytes) from whatever the underlying
kernel and driver uses. And, barring delays in application processing,
is completely free of overrun and underrun conditions, as well as free
of any potentially risky assumptions.
I think you're trying to hard to avoid using poll(). If you want to do
duplex I/O (be it with an audio device, or a network service, or
anything else) where the I/O streams are independent and need to be
serviced in a timely fashion, you have only two choices to do so correctly:
1) Use separate threads
2) Use poll or select with either non-blocking IO or (for OSS only)
ioctls to ensure that your reads/writes don't wind up sleeping
A third "incorrect" choice would be to:
3) Use non-blocking IO and busy waiting. (This would be an
incredibly bad choice, however.)
Note that blocking I/O cannot be used with duplex I/O *if* you have
timing constraints to meet. (Such as underflow or overflow conditions
to be worried about.)
-- Garrett
>
> Stephane Letz
>
>