>>
>>>
>>> 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.
Many applications use usleep() or something else to delay their  
operation but this is unnecessary and incorrect.
-----------------

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:

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...


Stephane Letz


Reply via email to