St?phane Letz wrote:
>>>
>>>>
>>>> b) use non-blocking IO
>>>>
>>>> Here's a sample processing loop that assumes all IO is blocking,
>>>> using poll() combined with the ioctls. Error handling is omitted.
>>>>
>>>> int audiofile = open("/dev/dsp", O_RDWR);
>>>>
>>>> for (;;) {
>>>> char readbuf[1024]; // pick whatever size you want here
>>>> char writebuf[1024]; // again, pick whatever size you want
>>>> char *rptr, *wptr;
>>>> int rcnt, wcnt;
>>>> sttruct pollfd pollfd;
>>>>
>>>> pollfd.fd = audiofile;
>>>> pollfd.events = 0;
>>>> pollfd.revents = 0;
>>>>
>>>> if (capturing && rcnt == 1024) {
>>>> // this empties the read buffer by sending capture data to a file
>>>> or whatever...
>>>> empty_readbuf(readbuf, 1024);
>>>> rcnt = 0;
>>>> rbuf = readbuf;
>>>> }
>>>>
>>>> if (playing && wcnt == 0) {
>>>> // this is your write size, fills writebuf with mixed audio data, etc.
>>>> fill_writebuf(writebuf, 1024);
>>>> wcnt = 1024;
>>>> wbuf = writebuf;
>>>> }
>>>>
>>>> if (capturing) {
>>>> pollfd.events |= POLLIN;
>>>> }
>>>>
>>>> if (playing) {
>>>> pollfd.events |= POLLOUT;
>>>> }
>>>>
>>>> if (pollfd.events == 0) {
>>>> /* no data to read *or* to write, why are we here! */
>>>> abort();
>>>> }
>>>>
>>>> if (poll(&pollfd, 1, 0) < 0) {
>>>> // error handling here
>>>> }
>>>>
>>>> if (pollfd.revents & POLLIN) {
>>>> // data ready for reading
>>>>
>>>> // if the file descriptor was setup for non-blocking reads
>>>> // then we could just call read() instead of using the ioctl
>>>> // error handling omitted
>>>> ioctl(audiofd, SNDCTL_DSP_GETISPACE, &info);
>>>> n = read(audiofd, rbuf, min(info.bytes, 1024 - rcnt));
>>>> rcnt += n;
>>>> rbuf += n;
>>>> }
>>>>
>>>> if (pollfd.revents & POLLOUT) {
>>>> // data ready for writing... similiar to reading case
>>>> ioctl(audiofd, SNDCTL_DSP_GETOSPACE, &info);
>>>> n = write(audiofd, rbuf, min(info.bytes, wcnt));
>>>> wcnt -= n;
>>>> wbuf += n;
>>>> }
>>>>
>>>> }
>>>>
>>>> If the above loop looks pretty simple, well it is. But that's all
>>>> you need to do. And it will work on *all* OSS versions, not just
>>>> Boomer or 4Front's code, but also FreeBSD, etc. If you want to
>>>> avoid the ioctls, you need to either use separate threads for read
>>>> and write processing, *or* you need to use nonblocking read() and
>>>> write() calls. (The poll() call will still block for you though.)
>>>>
>>>> -- Garrett
>>>>>
>>>
>>> Ok fine, but then there is no direct relationship between the buffer
>>> size (here 1024) for the audio cycle *and* the underlying fragment
>>> size + fragment number chosen at driver level right?
>>>
>>> So how do we know/control the overall input/output latency?? Do we
>>> have to use the SNDCTL_DSP_POLICY ioctl with a low value ? Is is
>>> supported on Boomer?
>>
>> Its ignored. There is way for applications to request a different
>> latency in Boomer than what the audio framework and drivers offer.
>> The drivers are already configured for fairly low latency. (Most of
>> the drivers are configured for about 175 Hz, or less than 10 msec of
>> latency.)
>>
>> You can certainly "guess" about the latency by using the return value
>> from GETBLKSIZE, or the fragment size returned by
>> SNDCTL_DSP_GETOSPACE. Its not a "concrete" promise though ...
>> merely a "best effort". Occasionally, due to rounding errors, two
>> fragments may be delivered at once, or you might need to wait for two
>> cycles in order to receive a fragment.
>>
>>>
>>> Said another way: should we completely *separate* parameters used by
>>> the user to choose the wanted cycle "buffer size" from the IO latency?
>>
>> Yes, I think so.
>>
>> For portability, I'd recommend using the SNDCTL_DSP_POLICY or
>> attempting to configure a low latency value using
>> SNDCTL_DSP_SETFRAGMENT, but you must not *rely* on either call doing
>> anything meaningful. The idea is simply to ensure that the latency
>> is low enough to meet your minimum latency requirements. If you're
>> requirements are reasonably flexible there (say not tighter than 20
>> or 30 msec) then probably even a default setting on all drivers will
>> be adequate.
>>
>> The highest latency Boomer driver, btw, is the audioens and audiopci
>> drivers. They have a default latency of 75 Hz, which corresponds to
>> about 13 msec fragment sizes. If you queue up two fragments ahead,
>> that's still less than 30 msec of latency.
>>
>> Note that on the capture side, the latency is just one fragment, so
>> 13 msec worst case (ignoring interrupt dispatch and processing
>> overheads).
>>
>> -- Garrett
>>
>
> OK.
>
> Speaking about the "poll" model instead of "blocking Read/Write", they
> were some discussion in the past :
> http://mailman.opensound.com/pipermail/oss-devel/2008-October/thread.html.
>
>
> And an mail exchange wit Hannu :
>
>> We discovered that jack is not sleeping when it has finished
>> processing its buffer. it is starting a new one. So we guess that
>> there is a synchronization issue between jack and the driver.
>> But there is the same problem with ossplay. So we guess that this
>> could come from the driver. I saw that jack just uses a write call,
>> without select, nor poll. So the problem would come from the driver
>> that flushes its descriptor too early.
>
> His answer:
>
> -----------------
> This is perfectly correct behaviour. Jack must call write immediately
> it has new block of data available. OSS will take care of sleeping
> inside the write call.
*Yes*, assuming you can tolerate the delay caused by the write. You
could conceivably issue a write() with 10MB or more of data. That
write() will not complete until all 10MB is queued though, so its
probably going to cause that particular thread to go to sleep for quite
a while...
If you don't want to sleep, you have two choices:
1) use nonblocking write (O_NDELAY or O_NONBLOCK), which is tricky
to get write
2) use poll() or somesuch in conjunction with ioctl()
SNDCTL_DSP_GETOSPACE or ISPACE.
> Many applications use usleep() or something else to delay their
> operation but this is unnecessary and incorrect.
usleep() or using artificial time based approaches is, as Hannu
indicates, incorrect. If possible you should use either blocking I/O,
or at least an event driven approach based around poll() or select().
> -----------------
>
> So I'm still not so clear why a simple "blocking" read/write could not
> be used? That is : just opening the device, setting the latency
> somewhat independantly from the buffer size we want to use later on,
> then use blocking read, with the *wanted* size, waiting data are
> available:
You can if you only need to write, or you only need to do read. If you
need to do both from the same thread, its more complicated because you
have to avoid overruns/underruns. See below.
>
> int audiofile = open("/dev/dsp", O_RDWR);
>
> // setup latency....
>
> in a LOOP :
>
> char readbuf[1024]; // pick whatever size you want here
> char writebuf[1024]; // pick whatever size you want here
>
> n = read(audiofd, readbuf, 1024);
> check n...
>
> ==> do our process with input data
>
> n = write(audiofd, writebuf, 1024);
> check n...
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