wvdsp.cc

00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *
00005  * One more attempt at making a decent stream for Linux /dev/dsp.
00006  * See wvdsp.h.
00007  * 
00008  * "See all that stuff in there, Homer?  I guess that's why _your_ robot
00009  * never worked!"
00010  */
00011 #include "wvdsp.h"
00012 #include "wvfork.h"
00013 #include <sys/ioctl.h>
00014 #include <sys/soundcard.h>
00015 #include <fcntl.h>
00016 #include <sched.h>
00017 #include <time.h>
00018 
00019 #define DO_RATEADJ 0
00020 
00021 static const char *AUDIO_DEVICE = "/dev/dsp";
00022 
00023 static int msec_lat(int frags, int frag_bits, int srate)
00024 {
00025     return frags * (1<<frag_bits) * 1000 / srate;
00026 }
00027 
00028 WvDsp::WvDsp(int msec_latency, int srate, int bits, bool stereo,
00029              bool readable, bool writable, bool _realtime, bool _oss)
00030     : log("DSP", WvLog::Debug2), rcircle(102400), wcircle(102400),
00031          inrate(bits/8 * (stereo ? 2 : 1), srate, srate), 
00032         outrate(bits/8 * (stereo ? 2 : 1), srate, srate)
00033 {
00034     is_realtime = _realtime;
00035 
00036     int mode = 0;
00037     
00038     outrate.orate_n = srate;
00039    
00040     assert(msec_latency >= 0);
00041     assert(srate >= 8000);
00042     assert(srate <= 48000);
00043     assert(bits == 8 || bits == 16);
00044     assert(readable || writable);
00045     
00046 #if DO_RATEADJ
00047     // the record clock should be the same as the playback clock, so it's
00048     // best to match our output rate to the input rate.  Of course, we can
00049     // only do this if we'll be inputting anything.
00050     if (readable)
00051         outrate.match_rate = &inrate;
00052 #endif
00053     
00054     if (readable && writable)
00055         mode = O_RDWR;
00056     else if (readable)
00057         mode = O_RDONLY;
00058     else if (writable)
00059         mode = O_WRONLY;
00060 
00061     // we do O_NONBLOCK in case someone is currently using the dsp
00062     // device and the particular one we're using can't be shared.
00063     if ((fd = open(AUDIO_DEVICE, mode | O_NONBLOCK)) < 0)
00064     {
00065         seterr(errno);
00066         return;
00067     }
00068     
00069     // now get rid of O_NONBLOCK, so write() and read() on our fd don't return
00070     // until we get our data.  Since select() doesn't work anyway with some
00071     // drivers, we'll have to cheat.
00072     fcntl(fd, F_SETFL, mode);
00073     
00074     // set the fragment size appropriately for the desired latency
00075     num_frags = 5;
00076     int frag_size_bits = 7; // log2 of fragment size
00077     int lat;
00078     if (msec_latency > 1000)
00079         msec_latency = 1000; // don't be _too_ ridiculous...
00080     while (msec_latency > (lat = msec_lat(num_frags, frag_size_bits, srate)))
00081     {
00082         if (frag_size_bits < 14 && msec_latency >= 2*lat)
00083             frag_size_bits++;
00084         else
00085             num_frags++;
00086     }
00087     
00088     log(WvLog::Debug, "With %s %s-bit frags, latency will be about %s ms.\n",
00089                       num_frags, frag_size_bits, lat);
00090     
00091     frag_size = (1 << frag_size_bits);
00092     if (!setioctl(SNDCTL_DSP_SETFRAGMENT, (num_frags << 16) | frag_size_bits))
00093         seterr("can't set frag size!");
00094     
00095     if (bits == 16)
00096     {
00097         if (!setioctl(SNDCTL_DSP_SETFMT, AFMT_S16_NE))
00098             seterr("can't set sample size!");
00099     }
00100     else if (bits == 8)
00101     {
00102         if (!setioctl(SNDCTL_DSP_SETFMT, AFMT_S8))
00103             seterr("can't set sample size!");
00104     }
00105         
00106     if (!setioctl(SNDCTL_DSP_CHANNELS, stereo ? 2 : 1))
00107         seterr("can't set number of channels!");
00108     
00109     if (!setioctl(SNDCTL_DSP_SPEED, srate))
00110         seterr("can't set sampling rate!");
00111 
00112     // in fact, the OSS docs say we're not allowed to have separate processes
00113     // doing reads and writes at the same time.  Unfortunately, ALSA seems
00114     // to _need_ that for decent real-time performance.  But you can toggle
00115     // it here :)
00116     if (_oss)
00117     {
00118         // start the read/writer process
00119         subproc(readable, writable);
00120     }
00121     else
00122     {
00123         // start the reader process
00124         if (readable) subproc(true, false);
00125         
00126         // start the writer process
00127         if (writable) subproc(false, true);
00128     }
00129 
00130     rloop.nowrite();
00131     wloop.noread();
00132     realtime(); // actually necessary, but a bit dangerous...
00133 }
00134 
00135 
00136 WvDsp::~WvDsp()
00137 {
00138     close();
00139 }
00140 
00141 
00142 bool WvDsp::pre_select(SelectInfo &si)
00143 {
00144     bool ret = false;
00145 
00146 /*
00147     size_t rleft = rcircle.used(), wleft = wcircle.used();
00148 
00149     if (rleft > 2*frag_size)
00150         log("read circle is filling! (%s = %s)\n", rleft, rleft/frag_size);
00151     if (wleft > 3*frag_size)
00152         log("write circle is filling! (%s = %s; %s)\n", 
00153             wleft, wleft/frag_size, ospace());
00154 */
00155 
00156     if (si.wants.readable)
00157     {
00158         rloop.drain();
00159         if (rcircle.used())
00160             return true;
00161         else
00162             ret |= rloop.pre_select(si);
00163     }
00164     
00165     if (si.wants.writable)
00166         return true;
00167     
00168     return ret;
00169 }
00170 
00171 
00172 bool WvDsp::post_select(SelectInfo &si)
00173 {
00174     bool ret = false;
00175     
00176     if (si.wants.readable)
00177     {
00178         if (rcircle.used())
00179             return true;
00180         else
00181             ret |= rloop.post_select(si);
00182     }
00183     
00184     return ret;
00185 }
00186 
00187 
00188 size_t WvDsp::uread(void *buf, size_t len)
00189 {
00190     if (len == 0)
00191         return 0;
00192     size_t avail = rcircle.used();
00193     
00194     // transfer from the magic circle into our rbuf, using the rate adjuster.
00195     {
00196         WvDynBuf silly;
00197         void *data = silly.alloc(avail);
00198         size_t got = rcircle.get(data, avail);
00199         silly.unalloc(avail - got);
00200 #if DO_RATEADJ
00201         inrate.encode(silly, rbuf);
00202 #else
00203         rbuf.merge(silly);
00204 #endif  
00205     }
00206     
00207     avail = rbuf.used();
00208     if (avail < len)
00209         len = avail;
00210     
00211     rbuf.move(buf, len);
00212     return len;
00213 }
00214 
00215 
00216 size_t WvDsp::uwrite(const void *buf, size_t len)
00217 {
00218     static time_t last_dump;
00219     
00220     if (len == 0)
00221         return 0;
00222     
00223     if (last_dump < time(NULL) - 1)
00224     {
00225         log(WvLog::Debug, "Writer rates: %s/%s; reader rates: %s/%s\n",
00226             outrate.getirate(), outrate.getorate(),
00227             inrate.getirate(), inrate.getorate());
00228         last_dump = time(NULL);
00229     }
00230    
00231 #if DO_RATEADJ
00232     outrate.flushmembuf(buf, len, wbuf);
00233 #else
00234     wbuf.put(buf, len);
00235 #endif
00236     
00237     size_t howmuch = wcircle.left();
00238     
00239     if (howmuch > wbuf.used())
00240         howmuch = wbuf.used();
00241     
00242     buf = wbuf.get(howmuch);
00243     wcircle.put(buf, howmuch);
00244     wloop.write("", 1);
00245     
00246     return len; // never let WvStreams buffer anything
00247 }
00248 
00249 
00250 bool WvDsp::isok() const
00251 {
00252     return (fd >= 0);
00253 }
00254 
00255 
00256 void WvDsp::close()
00257 {
00258     if (fd >= 0)
00259 	::close(fd);
00260     fd = -1;
00261 
00262     // this should wake up the subprocess(es) and ask them to die.
00263     rloop.close();
00264     wloop.close();
00265 }
00266 
00267 
00268 bool WvDsp::setioctl(int ctl, int param)
00269 {
00270     return ioctl(fd, ctl, &param) >= 0;
00271 }
00272 
00273 
00274 // set realtime scheduling priority
00275 void WvDsp::realtime()
00276 {
00277     if (is_realtime)
00278     {
00279         struct sched_param sch;
00280         memset(&sch, 0, sizeof(sch));
00281         sch.sched_priority = 1;
00282         if (sched_setscheduler(getpid(), SCHED_FIFO, &sch) < 0)
00283             seterr("can't set scheduler priority!");
00284     }
00285 }
00286 
00287 
00288 void WvDsp::subproc(bool reading, bool writing)
00289 {
00290     intTable fds(4);
00291     fds.add(new int(rloop.getrfd()), true);
00292     fds.add(new int(rloop.getwfd()), true);
00293     fds.add(new int(wloop.getrfd()), true);
00294     fds.add(new int(wloop.getwfd()), true);
00295     
00296     pid_t pid = wvfork(fds);
00297     if (pid < 0)
00298     {
00299         seterr(errno);
00300         return;
00301     }
00302     else if (pid > 0) // parent
00303         return;
00304 
00305     // otherwise, this is the child
00306 
00307     char buf[10240];
00308  
00309     realtime();
00310  
00311     rloop.noread();
00312     wloop.nowrite();
00313  
00314     if (!reading)
00315         rloop.close();
00316     if (!writing)
00317         wloop.close();
00318  
00319     while (isok() && (rloop.isok() || wloop.isok()))
00320     {
00321         if (reading)
00322         {
00323             size_t len = do_uread(buf, sizeof(buf));
00324             if (len)
00325             {
00326                 rcircle.put(buf, len);
00327                 rloop.write("", 1);
00328             }
00329         }
00330 
00331         if (writing)
00332         {
00333             wloop.drain();
00334             size_t avail;
00335             while ((avail = wcircle.used()) >= frag_size)
00336             {
00337                 if (avail > frag_size)
00338                     avail = frag_size;
00339                 size_t len = wcircle.get(buf, avail);
00340                 do_uwrite(buf, len);
00341             }
00342             if (!reading)
00343                 wloop.select(-1);
00344         }
00345     }
00346 
00347     _exit(0);
00348 }
00349 
00350 
00351 size_t WvDsp::ispace()
00352 {
00353     audio_buf_info info;
00354     
00355     if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) < 0)
00356     {
00357         log(WvLog::Error, "Error in GETISPACE\n");
00358         return 0;
00359     }
00360     
00361     return info.fragments;
00362 }
00363 
00364 
00365 size_t WvDsp::ospace()
00366 {
00367     audio_buf_info info;
00368     
00369     if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
00370     {
00371         log(WvLog::Error, "Error in GETOSPACE\n");
00372         return 0;
00373     }
00374     
00375     return num_frags - info.fragments;
00376 }
00377 
00378 
00379 size_t WvDsp::do_uread(void *buf, size_t len)
00380 {
00381     if (!len) return 0;
00382 
00383     if (len < frag_size)
00384         log(WvLog::Warning, "Reading less than frag size: %s/%s\n", len, frag_size);
00385 
00386     size_t i, i2;
00387     
00388     if (len > frag_size)
00389         len = frag_size;
00390     
00391     if ((i = ispace()) > 1)
00392     {
00393         if (i > num_frags * 2)
00394         {
00395             log("Resetting: frag count is broken! (%s)\n", i);
00396             ioctl(fd, SNDCTL_DSP_RESET, NULL);
00397         }
00398         else
00399         {
00400             i2 = i;
00401             while (i2-- > 1)
00402             {
00403                 char buf2[frag_size];
00404                 ::read(fd, buf2, frag_size);
00405             }
00406             //log("inbuf is filling up! (%s waiting)\n", i);
00407         }
00408     }
00409     
00410     // note: ALSA drivers sometimes read zero bytes even with stuff in the
00411     // buffer (sigh).  So that's not EOF in this case.
00412     int ret = ::read(fd, buf, len);
00413     if (ret < 0)
00414     {
00415         if (errno != EAGAIN)
00416             seterr(errno);
00417         return 0;
00418     }
00419 
00420     if (ret && ret < (int)len && ret < (int)frag_size)
00421         log("inbuf underflow (%s/%s)!\n", ret, len);
00422 
00423     return ret;
00424 }
00425 
00426 
00427 size_t WvDsp::do_uwrite(const void *buf, size_t len)
00428 {
00429     if (!len) return 0;
00430     
00431     if (len < frag_size)
00432         log(WvLog::Warning, "Writing less than frag size: %s/%s\n",
00433             len, frag_size);
00434     
00435     int o = ospace(), o2;
00436     
00437     if (o < 2)
00438     {
00439         o2 = o;
00440         while (o2++ < 2)
00441         {
00442             char buf2[frag_size];
00443             memset(buf2, 0, sizeof(buf2));
00444 	    ::write(fd, buf2, frag_size);
00445         }
00446         //log("outbuf is almost empty! (%s waiting)\n", o);
00447     }
00448 
00449     if (o >= (int)num_frags-1)
00450     {
00451         //log("outbuf overflowing (%s): skipping write.\n", o);
00452         return len;
00453     }
00454     
00455     size_t ret = ::write(fd, buf, len);
00456     if (ret < 0)
00457     {
00458         log("Error: %s\n", errno);
00459         if (errno != EAGAIN)
00460             seterr(errno);
00461         return len; // avoid using WvStreams buffer
00462     }
00463 
00464 /*
00465     if (ret < len)
00466         log("outbuf overflow (%s/%s)!\n", ret, len);
00467 */
00468 
00469     return len; // avoid using WvStreams buffer
00470 }
00471 
00472 

Generated on Mon Feb 5 10:54:29 2007 for WvStreams by  doxygen 1.5.1