00001
00002
00003
00004
00005
00006
00007
00008
00009
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
00048
00049
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
00062
00063 if ((fd = open(AUDIO_DEVICE, mode | O_NONBLOCK)) < 0)
00064 {
00065 seterr(errno);
00066 return;
00067 }
00068
00069
00070
00071
00072 fcntl(fd, F_SETFL, mode);
00073
00074
00075 num_frags = 5;
00076 int frag_size_bits = 7;
00077 int lat;
00078 if (msec_latency > 1000)
00079 msec_latency = 1000;
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
00113
00114
00115
00116 if (_oss)
00117 {
00118
00119 subproc(readable, writable);
00120 }
00121 else
00122 {
00123
00124 if (readable) subproc(true, false);
00125
00126
00127 if (writable) subproc(false, true);
00128 }
00129
00130 rloop.nowrite();
00131 wloop.noread();
00132 realtime();
00133 }
00134
00135
00136 WvDsp::~WvDsp()
00137 {
00138 close();
00139 }
00140
00141
00142 void WvDsp::pre_select(SelectInfo &si)
00143 {
00144
00145
00146
00147
00148
00149
00150
00151
00152
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
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;
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
00257 rloop.close();
00258 wloop.close();
00259 }
00260
00261
00262 bool WvDsp::setioctl(int ctl, int param)
00263 {
00264 return ioctl(fd, ctl, ¶m) >= 0;
00265 }
00266
00267
00268
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)
00297 return;
00298
00299
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
00401 }
00402 }
00403
00404
00405
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
00441 }
00442
00443 if (o >= (int)num_frags-1)
00444 {
00445
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;
00456 }
00457
00458
00459
00460
00461
00462
00463 return len;
00464 }
00465
00466