St?phane Letz wrote:
>>
>> This would work *if* you were willing to tolerate the fact that your
>> write of 1024 might take a while. (Or the read.) But this can cause
>> underruns on either the write or the read side (whichever side you
>> *aren't* writing or reading to), especially if your read or write
>> request is larger than a fragment or two. (Actually, as I think
>> about it, using a *small* buffer -- smaller than the fragment size --
>> should be free of this problem, but you'll have higher processing
>> overhead in your application, and it makes a bad assumption about the
>> input and output fragment sizes being the same, which may not always
>> be true.)
>>
>> In other words, while you're waiting for your write() to complete,
>> the capture data may become available and the capture queue can
>> overfill, causing an overrun and lost capture data.
>>
>> Conversely, while you're waiting for read() to complete, the playback
>> buffer could drain completely, causing an underrun and the resulting
>> popping noises.
>>
>> Using multiple threads (one for reading, and one for writing) solves
>> this problem cleanly -- each thread will do the processing as soon as
>> the underlying kernel code indicates availability, and then you *can*
>> skip most of the compexity associated with poll and ioctl, and just
>> use normal blocking IO. (But then you should open two file
>> descriptors, one with O_RDONLY, and one with O_WRONLY).
>>
>> It should be obvious that you get a big simplification from using
>> separate threads -- if you're OS can support it and you don't have
>> other reasons to require synchronization between capture and playback
>> processing.
>>
>> -- Garrett
>>
>
> I understand the need for 2 threads in "general" case, but here the
> requeriments are somewhat restricted. JACK server runs usually in
> *duplex* mode, using a card and using input and output devices of the
> *same* card. That is we assume both devices runs completely
> synchronized (otherwise we are not going to try having a system meant
> for completely synchronous execution to work reliably....)
Duplex works fine with multiple threads. You can have two file
descriptors, one used by the reading thread (opened O_RDONLY) and one
used by the playback thread (opened O_WRONLY). You don't have to use
only a single file descriptor to make this work.
You should *not* assume that the two sides (read vs. write) are
synchronized. On a single device, they might have different
fragmentation boundaries, and might use completely independent clocks!
(This is not as uncommon as you might think.)
I think the root of your troubles here are "assumptions". Well written
code won't have any such assumptions and will work well regardless of
the details of the device driver, or even the underlying implementation.
>
> That said, the system is supposed to consume and produce frames at the
> right time! Said in another way, if the execution of the client graph
> takes toom much time, then we have an "Xrun (overrun/underrun)" which
> is perfectly normal... What else could we do? We can possible smooth
> "occasionnal" CPU spike a bit by prefilling the output buffer, but in
> the general case, the audio cycle : Read / Process/ Write is not
> supposed to take more time than represented but the "buffer size" used
> in the graph processing.
*That* is a bad assumption. You're trying to get a promise/guarantee
from the kernel that simply doesn't exist. And, you don't actually need
the promise if your code is written properly.
To prevent overruns or underruns, you need to either restrict your
writes or reads to the size that can be performed without blocking
(which you can learn with the SNDCTL_DSP_GETOSPACE/ISPACE ioctls), or
use non-blocking IO. If you want to do this for both read and write in
a single thread, you'll need to also use poll().
All the other approaches you're thinking of are hacks that *might* work
for some of the devices some of the time, but which are technically
"incorrect" and will almost certainly fail for *some* devices, at least
some of the time.
>
> So to summarize, we prefill output a bit to avoid underruns, then the
> Read / Process/ Write is supposed to work at the right speed. And this
> one thread model simplify others parts: we don't need to use additonal
> "ring-buffers" or "queues" to move data between the thread that calls
> the graph "Process" and the one that would call Read/Write.
Okay, one thread will work for you, but you need to use the poll or
ioctl or nonblocking approaches I've already pointed out then. My
sample code should help you out there.
Actually, with your crazy processing considerations, probably what we
should do is look into getting you mmap(2) support, where you can just
run completely disconnected from the kernel code, and use whatever speed
and rate is most comfortable for your server. Unfortunately, the
mmap(2) syscall isn't supported by Boomer Phase I (it will be in Phase
II), and in any case it isn't portable to all drivers. (4Front's code
only supports it on Linux, and only for some devices ... e.g. USB audio
probably doesn't support it because USB devices don't use memory mapped IO.)
-- Garrett
>
> Stephane Letz
>
>
>