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 void WvDsp::pre_select(SelectInfo &si)
00143 {
00144 
00145 /*
00146     size_t rleft = rcircle.used(), wleft = wcircle.used();
00147 
00148     if (rleft > 2*frag_size)
00149         log("read circle is filling! (%s = %s)\n", rleft, rleft/frag_size);
00150     if (wleft > 3*frag_size)
00151         log("write circle is filling! (%s = %s; %s)\n", 
00152             wleft, wleft/frag_size, ospace());
00153 */
00154 
00155     if (si.wants.readable)
00156     {
00157         rloop.drain();
00158         if (rcircle.used())
00159             return;
00160         else
00161             rloop.pre_select(si);
00162     }
00163 }
00164 
00165 
00166 bool WvDsp::post_select(SelectInfo &si)
00167 {
00168     bool ret = false;
00169     
00170     if (si.wants.readable)
00171     {
00172         if (rcircle.used())
00173             return true;
00174         else
00175             ret |= rloop.post_select(si);
00176     }
00177     
00178     return ret;
00179 }
00180 
00181 
00182 size_t WvDsp::uread(void *buf, size_t len)
00183 {
00184     if (len == 0)
00185         return 0;
00186     size_t avail = rcircle.used();
00187     
00188     // transfer from the magic circle into our rbuf, using the rate adjuster.
00189     {
00190         WvDynBuf silly;
00191         void *data = silly.alloc(avail);
00192         size_t got = rcircle.get(data, avail);
00193         silly.unalloc(avail - got);
00194 #if DO_RATEADJ
00195         inrate.encode(silly, rbuf);
00196 #else
00197         rbuf.merge(silly);
00198 #endif  
00199     }
00200     
00201     avail = rbuf.used();
00202     if (avail < len)
00203         len = avail;
00204     
00205     rbuf.move(buf, len);
00206     return len;
00207 }
00208 
00209 
00210 size_t WvDsp::uwrite(const void *buf, size_t len)
00211 {
00212     static time_t last_dump;
00213     
00214     if (len == 0)
00215         return 0;
00216     
00217     if (last_dump < time(NULL) - 1)
00218     {
00219         log(WvLog::Debug, "Writer rates: %s/%s; reader rates: %s/%s\n",
00220             outrate.getirate(), outrate.getorate(),
00221             inrate.getirate(), inrate.getorate());
00222         last_dump = time(NULL);
00223     }
00224    
00225 #if DO_RATEADJ
00226     outrate.flushmembuf(buf, len, wbuf);
00227 #else
00228     wbuf.put(buf, len);
00229 #endif
00230     
00231     size_t howmuch = wcircle.left();
00232     
00233     if (howmuch > wbuf.used())
00234         howmuch = wbuf.used();
00235     
00236     buf = wbuf.get(howmuch);
00237     wcircle.put(buf, howmuch);
00238     wloop.write("", 1);
00239     
00240     return len; // never let WvStreams buffer anything
00241 }
00242 
00243 
00244 bool WvDsp::isok() const
00245 {
00246     return (fd >= 0);
00247 }
00248 
00249 
00250 void WvDsp::close()
00251 {
00252     if (fd >= 0)
00253 	::close(fd);
00254     fd = -1;
00255 
00256     // this should wake up the subprocess(es) and ask them to die.
00257     rloop.close();
00258     wloop.close();
00259 }
00260 
00261 
00262 bool WvDsp::setioctl(int ctl, int param)
00263 {
00264     return ioctl(fd, ctl, &param) >= 0;
00265 }
00266 
00267 
00268 // set realtime scheduling priority
00269 void WvDsp::realtime()
00270 {
00271     if (is_realtime)
00272     {
00273         struct sched_param sch;
00274         memset(&sch, 0, sizeof(sch));
00275         sch.sched_priority = 1;
00276         if (sched_setscheduler(getpid(), SCHED_FIFO, &sch) < 0)
00277             seterr("can't set scheduler priority!");
00278     }
00279 }
00280 
00281 
00282 void WvDsp::subproc(bool reading, bool writing)
00283 {
00284     intTable fds(4);
00285     fds.add(new int(rloop.getrfd()), true);
00286     fds.add(new int(rloop.getwfd()), true);
00287     fds.add(new int(wloop.getrfd()), true);
00288     fds.add(new int(wloop.getwfd()), true);
00289     
00290     pid_t pid = wvfork(fds);
00291     if (pid < 0)
00292     {
00293         seterr(errno);
00294         return;
00295     }
00296     else if (pid > 0) // parent
00297         return;
00298 
00299     // otherwise, this is the child
00300 
00301     char buf[10240];
00302  
00303     realtime();
00304  
00305     rloop.noread();
00306     wloop.nowrite();
00307  
00308     if (!reading)
00309         rloop.close();
00310     if (!writing)
00311         wloop.close();
00312  
00313     while (isok() && (rloop.isok() || wloop.isok()))
00314     {
00315         if (reading)
00316         {
00317             size_t len = do_uread(buf, sizeof(buf));
00318             if (len)
00319             {
00320                 rcircle.put(buf, len);
00321                 rloop.write("", 1);
00322             }
00323         }
00324 
00325         if (writing)
00326         {
00327             wloop.drain();
00328             size_t avail;
00329             while ((avail = wcircle.used()) >= frag_size)
00330             {
00331                 if (avail > frag_size)
00332                     avail = frag_size;
00333                 size_t len = wcircle.get(buf, avail);
00334                 do_uwrite(buf, len);
00335             }
00336             if (!reading)
00337                 wloop.select(-1);
00338         }
00339     }
00340 
00341     _exit(0);
00342 }
00343 
00344 
00345 size_t WvDsp::ispace()
00346 {
00347     audio_buf_info info;
00348     
00349     if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) < 0)
00350     {
00351         log(WvLog::Error, "Error in GETISPACE\n");
00352         return 0;
00353     }
00354     
00355     return info.fragments;
00356 }
00357 
00358 
00359 size_t WvDsp::ospace()
00360 {
00361     audio_buf_info info;
00362     
00363     if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
00364     {
00365         log(WvLog::Error, "Error in GETOSPACE\n");
00366         return 0;
00367     }
00368     
00369     return num_frags - info.fragments;
00370 }
00371 
00372 
00373 size_t WvDsp::do_uread(void *buf, size_t len)
00374 {
00375     if (!len) return 0;
00376 
00377     if (len < frag_size)
00378         log(WvLog::Warning, "Reading less than frag size: %s/%s\n", len, frag_size);
00379 
00380     size_t i, i2;
00381     
00382     if (len > frag_size)
00383         len = frag_size;
00384     
00385     if ((i = ispace()) > 1)
00386     {
00387         if (i > num_frags * 2)
00388         {
00389             log("Resetting: frag count is broken! (%s)\n", i);
00390             ioctl(fd, SNDCTL_DSP_RESET, NULL);
00391         }
00392         else
00393         {
00394             i2 = i;
00395             while (i2-- > 1)
00396             {
00397                 char buf2[frag_size];
00398                 ::read(fd, buf2, frag_size);
00399             }
00400             //log("inbuf is filling up! (%s waiting)\n", i);
00401         }
00402     }
00403     
00404     // note: ALSA drivers sometimes read zero bytes even with stuff in the
00405     // buffer (sigh).  So that's not EOF in this case.
00406     int ret = ::read(fd, buf, len);
00407     if (ret < 0)
00408     {
00409         if (errno != EAGAIN)
00410             seterr(errno);
00411         return 0;
00412     }
00413 
00414     if (ret && ret < (int)len && ret < (int)frag_size)
00415         log("inbuf underflow (%s/%s)!\n", ret, len);
00416 
00417     return ret;
00418 }
00419 
00420 
00421 size_t WvDsp::do_uwrite(const void *buf, size_t len)
00422 {
00423     if (!len) return 0;
00424     
00425     if (len < frag_size)
00426         log(WvLog::Warning, "Writing less than frag size: %s/%s\n",
00427             len, frag_size);
00428     
00429     int o = ospace(), o2;
00430     
00431     if (o < 2)
00432     {
00433         o2 = o;
00434         while (o2++ < 2)
00435         {
00436             char buf2[frag_size];
00437             memset(buf2, 0, sizeof(buf2));
00438 	    ::write(fd, buf2, frag_size);
00439         }
00440         //log("outbuf is almost empty! (%s waiting)\n", o);
00441     }
00442 
00443     if (o >= (int)num_frags-1)
00444     {
00445         //log("outbuf overflowing (%s): skipping write.\n", o);
00446         return len;
00447     }
00448     
00449     size_t ret = ::write(fd, buf, len);
00450     if (ret < 0)
00451     {
00452         log("Error: %s\n", errno);
00453         if (errno != EAGAIN)
00454             seterr(errno);
00455         return len; // avoid using WvStreams buffer
00456     }
00457 
00458 /*
00459     if (ret < len)
00460         log("outbuf overflow (%s/%s)!\n", ret, len);
00461 */
00462 
00463     return len; // avoid using WvStreams buffer
00464 }
00465 
00466 

Generated on Thu Jan 24 16:50:56 2008 for WvStreams by  doxygen 1.5.4