aboutsummaryrefslogtreecommitdiffstats
path: root/oss.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'oss.cpp')
-rw-r--r--oss.cpp358
1 files changed, 358 insertions, 0 deletions
diff --git a/oss.cpp b/oss.cpp
new file mode 100644
index 0000000..af41cf2
--- /dev/null
+++ b/oss.cpp
@@ -0,0 +1,358 @@
+#if defined(HAVE_SOUNDCARD_H) || defined(HAVE_SYS_SOUNDCARD_H)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SOUNDCARD_H
+# include <soundcard.h>
+#endif
+#ifdef HAVE_SYS_SOUNDCARD_H
+# include <sys/soundcard.h>
+#endif
+
+#include "sound.h"
+#include "oss.moc"
+
+#ifndef OSS_GETVERSION
+#define OSS_GETVERSION _IOR('M',118,int)
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+Soundcard::Soundcard(const char *dev)
+{
+ if (dev)
+ strcpy(devname,dev);
+ else
+ strcpy(devname,"/dev/dsp");
+
+ driver_name[0] = '\0';
+
+ stat = STATUS_CLOSED;
+ get_capabilities();
+ channels = 1;
+ rate = 22050;
+ fd = -1;
+}
+
+Soundcard::~Soundcard()
+{
+ /* nothing */
+}
+
+int
+Soundcard::start_record()
+{
+ switch (stat) {
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return -1;
+ return open_dev(TRUE);
+ case STATUS_RECORD:
+ return 0;
+ case STATUS_PLAYBACK:
+ close_dev();
+ return open_dev(TRUE);
+ }
+ return -1;
+}
+
+int
+Soundcard::start_playback()
+{
+ switch (stat) {
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return -1;
+ return open_dev(FALSE);
+ case STATUS_RECORD:
+ close_dev();
+ return open_dev(FALSE);
+ case STATUS_PLAYBACK:
+ return 0;
+ }
+ return -1;
+}
+
+int
+Soundcard::kill_buffer()
+{
+ ioctl(fd,SNDCTL_DSP_RESET,0);
+ return 0;
+}
+
+int
+Soundcard::stop()
+{
+ if (stat != STATUS_CLOSED)
+ close_dev();
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+Soundcard::get_capabilities()
+{
+ int i,dsp;
+ int try_afmt;
+ int try_channels;
+
+ afmt = 0;
+ if (-1 != (dsp = open(devname, O_RDONLY))) {
+
+ ioctl(dsp, SNDCTL_DSP_SETFMT, &afmt); /* current */
+ ioctl(dsp, SNDCTL_DSP_GETFMTS, &afmt_hw); /* hardware cap */
+ afmt_sw = 0;
+
+ for (i = 0; i < 16 /* XXX */; i++) {
+ try_afmt = (1<<i);
+ if (-1 == ioctl(dsp, SNDCTL_DSP_SETFMT, &try_afmt))
+ continue;
+ if (try_afmt != (1<<i))
+ continue;
+ afmt_sw |= try_afmt;
+ }
+
+ try_channels = 2;
+ if (-1 != ioctl(dsp, SNDCTL_DSP_CHANNELS, &try_channels) &&
+ 2 == try_channels)
+ channels_hw = 2;
+ else
+ channels_hw = 1;
+
+ /* version check */
+ if (-1 == ioctl(dsp,OSS_GETVERSION,&i)) {
+ strcpy(driver_name,"OSS (version unknown)");
+ } else {
+ sprintf(driver_name,"OSS %d.%d.%d%c",
+ (i>>16) & 0xff,(i>>8) & 0xff,(i>>4) & 0xf,(i&0xf)+'a');
+ }
+
+ close(dsp);
+ init_done = 1;
+
+ } else {
+ init_done = 0;
+ }
+}
+
+int
+Soundcard::has_channels()
+{
+ if (!init_done)
+ return -1;
+ return channels_hw;
+}
+
+int
+Soundcard::has_format(int f)
+{
+ if (!init_done)
+ return -1;
+ switch (f) {
+ case FMT_8BIT:
+ return (afmt_hw & AFMT_U8) ? 1 : 0;
+ break;
+ case FMT_16BIT:
+ return (afmt_hw & AFMT_S16_LE) ? 1 : 0;
+ break;
+ case FMT_MULAW:
+ case FMT_ALAW:
+ default:
+ return 0;
+ }
+}
+
+char*
+Soundcard::driver()
+{
+ return driver_name;
+}
+
+int
+Soundcard::open_dev(int record)
+{
+ struct SOUNDPARAMS p;
+ int frag,rrate;
+
+ if (-1 == (fd = open(devname,record ? O_RDONLY : O_WRONLY)))
+ goto err;
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+
+ /* try to get ~50 ms latency */
+ blocksize = 50*channels*rate/1000;
+ if (afmt == AFMT_U16_BE || afmt == AFMT_S16_BE ||
+ afmt == AFMT_U16_LE || afmt == AFMT_S16_LE)
+ blocksize *= 2;
+ for (frag = 0; blocksize != 1; frag++)
+ blocksize >>= 1;
+#if 0
+ fprintf(stderr,"asking for %d byte blocksize\n",1 << frag);
+#endif
+ frag |= 0x7fff0000;
+ if (-1 == ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ perror("ioctl SNDCTL_DSP_SETFRAGMENT");
+
+ rrate = rate;
+ if (-1 == ioctl(fd, SNDCTL_DSP_SETFMT, &afmt)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (-1 == ioctl(fd, SNDCTL_DSP_CHANNELS, &channels)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (-1 == ioctl(fd, SNDCTL_DSP_SPEED, &rrate)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (-1 == ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blocksize)) {
+ perror("ioctl SNDCTL_DSP_SETFMT");
+ goto err;
+ }
+ if (0 == blocksize)
+ blocksize = 4096;
+ if (rrate != rate) {
+ fprintf(stderr,"sample rate: asked for %d, hardware uses %d. ",
+ rate,rrate);
+ if (abs(rate-rrate)*100 < rate) {
+ fprintf(stderr,"that's fine (diff <1%%).\n");
+ } else {
+ fprintf(stderr,"way off, using hardware rate.\n");
+ rate = rrate;
+ }
+ }
+
+ latency = blocksize*1000/channels/rate;
+ if (afmt == AFMT_U16_BE || afmt == AFMT_S16_BE ||
+ afmt == AFMT_U16_LE || afmt == AFMT_S16_LE)
+ latency = latency/2;
+
+ telmi = new QSocketNotifier
+ (fd, record ? QSocketNotifier::Read : QSocketNotifier::Write);
+ QObject::connect(telmi,SIGNAL(activated(int)),
+ this, SLOT(sounddata(int)));
+
+ stat = record ? STATUS_RECORD : STATUS_PLAYBACK;
+#if 0
+ fprintf(stderr,"%s (format=%d, %s, rate=%d, blocksize=%d, latency=%d ms)\n",
+ record ? "recording" : "playback",
+ afmt,
+ (channels == 2) ? "stereo" : "mono",
+ rate, blocksize, latency);
+#endif
+ p.channels = channels;
+ p.rate = rate;
+ p.blocksize = blocksize;
+ p.latency = latency;
+ switch (afmt) {
+ case AFMT_U8: p.format = FMT_8BIT; break;
+ case AFMT_S16_LE: p.format = FMT_16BIT; break;
+ default: fprintf(stderr,"oops(open): unsupported sound format\n"); exit(1);
+ }
+ emit newparams(&p);
+
+ if (record) {
+ trigger = ~PCM_ENABLE_INPUT;
+ ioctl(fd,SNDCTL_DSP_SETTRIGGER,&trigger);
+ trigger = PCM_ENABLE_INPUT;
+ ioctl(fd,SNDCTL_DSP_SETTRIGGER,&trigger);
+ }
+ return 0;
+
+err:
+ if (-1 != fd)
+ close(fd);
+ stat = STATUS_CLOSED;
+ fd = -1;
+ return -1;
+}
+
+void
+Soundcard::close_dev()
+{
+ close(fd);
+ fd = -1;
+ stat = STATUS_CLOSED;
+
+ delete telmi;
+ return;
+}
+
+void
+Soundcard::setparams(struct SOUNDPARAMS *p)
+{
+ rate = p->rate;
+ channels = p->channels;
+ switch (p->format) {
+ case FMT_8BIT: afmt = AFMT_U8; break;
+ case FMT_16BIT: afmt = AFMT_S16_LE; break;
+ default: fprintf(stderr,"oops(set): unsupported sound format\n"); exit(1);
+ }
+
+ switch (stat) {
+ case STATUS_RECORD:
+ close_dev();
+ open_dev(TRUE);
+ break;
+ case STATUS_PLAYBACK:
+ close_dev();
+ open_dev(FALSE);
+ break;
+ case STATUS_CLOSED:
+ if (!init_done)
+ get_capabilities();
+ if (!init_done)
+ return;
+ if (0 == open_dev(TRUE))
+ close_dev();
+ break;
+ }
+}
+
+void
+Soundcard::sounddata(int s)
+{
+ int rc,have;
+
+ switch (stat) {
+ case STATUS_RECORD:
+ /* read */
+ for (have = 0; have < blocksize;) {
+ rc = read(fd,buffer+have,blocksize-have);
+ switch (rc) {
+ case -1:
+ if (EINTR != errno) {
+ perror("read sound");
+ exit(1);
+ }
+ break;
+ case 0:
+ fprintf(stderr,"Huh? got 0 bytes from sound device?\n");
+ exit(1);
+ default:
+ have += rc;
+ }
+ }
+ emit senddata((void*)buffer);
+ break;
+ case STATUS_PLAYBACK:
+ emit receivedata((void*)buffer);
+ if (-1 != fd)
+ write(fd,buffer,blocksize);
+ emit senddata((void*)buffer); /* fft :-) */
+ break;
+ }
+}
+
+#endif /* SOUNDCARD_H */