Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | File List | Namespace Members | Class Members | File Members | Related Pages

wvdsp.cc

Go to the documentation of this file.
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 Tue Oct 5 01:09:20 2004 for WvStreams by doxygen 1.3.7