summaryrefslogtreecommitdiffstats
path: root/plugins/oss-sound/oss-sound.cpp
diff options
context:
space:
mode:
authorMichele Calgaro <michele.calgaro@yahoo.it>2022-05-30 19:40:31 +0900
committerMichele Calgaro <michele.calgaro@yahoo.it>2022-05-31 01:17:58 +0900
commitd95a4fea540b371fa86493d069fdbd54f33c5b40 (patch)
tree079b038ab559439eb7ded40a07bd79fd92926b3b /plugins/oss-sound/oss-sound.cpp
parente54890e0480e5adee69f5220a7c6dd072bbd75ea (diff)
downloadtderadio-d95a4fea540b371fa86493d069fdbd54f33c5b40.tar.gz
tderadio-d95a4fea540b371fa86493d069fdbd54f33c5b40.zip
Standardize folder structure.
Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
Diffstat (limited to 'plugins/oss-sound/oss-sound.cpp')
-rw-r--r--plugins/oss-sound/oss-sound.cpp991
1 files changed, 991 insertions, 0 deletions
diff --git a/plugins/oss-sound/oss-sound.cpp b/plugins/oss-sound/oss-sound.cpp
new file mode 100644
index 0000000..00801c0
--- /dev/null
+++ b/plugins/oss-sound/oss-sound.cpp
@@ -0,0 +1,991 @@
+/***************************************************************************
+ oss-sound.cpp - description
+ -------------------
+ begin : Sun Mar 21 2004
+ copyright : (C) 2004 by Martin Witte
+ email : witte@kawo1.rwth-aachen.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#include "oss-sound.h"
+
+#include "../../src/include/aboutwidget.h"
+#include <tdelocale.h>
+#include <tdeaboutdata.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <math.h>
+#include <errno.h>
+
+#include "oss-sound-configuration.h"
+#include "../../src/include/utils.h"
+
+///////////////////////////////////////////////////////////////////////
+//// plugin library functions
+
+PLUGIN_LIBRARY_FUNCTIONS(OSSSoundDevice, "tderadio-oss-sound", i18n("Open Sound System (OSS) Support"));
+
+/////////////////////////////////////////////////////////////////////////////
+
+struct _lrvol { unsigned char l, r; short dummy; };
+
+OSSSoundDevice::OSSSoundDevice(const TQString &name)
+ : TQObject(NULL, NULL),
+ PluginBase(name, i18n("TDERadio OSS Sound Plugin")),
+ m_DSPDeviceName(""),
+ m_MixerDeviceName(""),
+ m_DSP_fd(-1),
+ m_Mixer_fd(-1),
+ m_DuplexMode(DUPLEX_UNKNOWN),
+ m_DSPFormat(),
+ m_PassivePlaybackStreams(),
+ m_PlaybackStreamID(),
+ m_CaptureStreamID(),
+ m_BufferSize(65536),
+ m_PlaybackBuffer(m_BufferSize),
+ m_CaptureBuffer(m_BufferSize),
+ m_CaptureRequestCounter(0),
+ m_CapturePos(0),
+ m_CaptureStartTime(0),
+ //m_PlaybackSkipCount(0),
+ m_CaptureSkipCount(0),
+ m_EnablePlayback(true),
+ m_EnableCapture(true)
+{
+ TQObject::connect(&m_PollingTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotPoll()));
+}
+
+
+OSSSoundDevice::~OSSSoundDevice()
+{
+ stopCapture(m_CaptureStreamID);
+ stopPlayback(m_PlaybackStreamID);
+ closeDSPDevice();
+ closeMixerDevice();
+}
+
+
+bool OSSSoundDevice::connectI(Interface *i)
+{
+ bool a = PluginBase::connectI(i);
+ bool b = ISoundStreamClient::connectI(i);
+ return a || b;
+}
+
+
+bool OSSSoundDevice::disconnectI(Interface *i)
+{
+ bool a = PluginBase::disconnectI(i);
+ bool b = ISoundStreamClient::disconnectI(i);
+ return a || b;
+}
+
+void OSSSoundDevice::noticeConnectedI (ISoundStreamServer *s, bool pointer_valid)
+{
+ ISoundStreamClient::noticeConnectedI(s, pointer_valid);
+ if (s && pointer_valid) {
+ s->register4_sendReleasePlayback(this);
+ s->register4_sendReleaseCapture(this);
+ s->register4_sendPlaybackVolume(this);
+ s->register4_sendCaptureVolume(this);
+ s->register4_queryPlaybackVolume(this);
+ s->register4_queryCaptureVolume(this);
+ s->register4_sendStartPlayback(this);
+ s->register4_sendPausePlayback(this);
+ s->register4_sendStopPlayback(this);
+ s->register4_queryIsPlaybackRunning(this);
+ s->register4_sendStartCaptureWithFormat(this);
+ s->register4_sendStopCapture(this);
+ s->register4_queryIsCaptureRunning(this);
+ s->register4_notifySoundStreamClosed(this);
+ s->register4_notifySoundStreamRedirected(this);
+ s->register4_notifySoundStreamData(this);
+ }
+}
+
+// PluginBase
+
+void OSSSoundDevice::saveState (TDEConfig *c) const
+{
+ c->setGroup(TQString("oss-sound-") + PluginBase::name());
+
+ c->writeEntry("dsp-device", m_DSPDeviceName);
+ c->writeEntry("mixer-device", m_MixerDeviceName);
+ c->writeEntry("enable-playback", m_EnablePlayback);
+ c->writeEntry("enable-capture", m_EnableCapture);
+ c->writeEntry("buffer-size", m_BufferSize);
+ c->writeEntry("soundstreamclient-id", m_SoundStreamClientID);
+}
+
+
+void OSSSoundDevice::restoreState (TDEConfig *c)
+{
+ c->setGroup(TQString("oss-sound-") + PluginBase::name());
+
+ m_EnablePlayback = c->readBoolEntry("enable-playback", true);
+ m_EnableCapture = c->readBoolEntry("enable-capture", true);
+ m_BufferSize = c->readNumEntry ("buffer-size", 65536);
+
+ setDSPDeviceName (c->readEntry ("dsp-device", "/dev/dsp"));
+ setMixerDeviceName (c->readEntry ("mixer-device", "/dev/mixer"));
+
+ m_PlaybackBuffer.resize(m_BufferSize);
+ m_CaptureBuffer.resize(m_BufferSize);
+
+ setSoundStreamClientID(c->readEntry("soundstreamclient-id", getSoundStreamClientID()));
+
+ emit sigUpdateConfig();
+}
+
+
+void OSSSoundDevice::setMixerDeviceName(const TQString &dev_name)
+{
+ if (m_MixerDeviceName != dev_name) {
+ m_MixerDeviceName = dev_name;
+ if (m_Mixer_fd >= 0)
+ openMixerDevice(true);
+ getMixerChannels(SOUND_MIXER_DEVMASK, m_PlaybackChannels, m_revPlaybackChannels);
+ getMixerChannels(SOUND_MIXER_RECMASK, m_CaptureChannels, m_revCaptureChannels);
+ notifyPlaybackChannelsChanged(m_SoundStreamClientID, m_PlaybackChannels);
+ notifyCaptureChannelsChanged(m_SoundStreamClientID, m_CaptureChannels);
+ }
+}
+
+
+ConfigPageInfo OSSSoundDevice::createConfigurationPage()
+{
+ OSSSoundConfiguration *conf = new OSSSoundConfiguration(NULL, this);
+ TQObject::connect(this, TQT_SIGNAL(sigUpdateConfig()), conf, TQT_SLOT(slotUpdateConfig()));
+ return ConfigPageInfo (conf,
+ i18n("OSS Sound"),
+ i18n("OSS Sound Device Options"),
+ "tderadio_oss");
+}
+
+
+AboutPageInfo OSSSoundDevice::createAboutPage()
+{
+/* TDEAboutData aboutData("tderadio",
+ NULL,
+ NULL,
+ I18N_NOOP("OSS Sound Plugin for TDERadio"),
+ TDEAboutData::License_GPL,
+ "(c) 2004 Martin Witte",
+ 0,
+ "http://sourceforge.net/projects/tderadio",
+ 0);
+ aboutData.addAuthor("Martin Witte", "", "witte@kawo1.rwth-aachen.de");
+
+ return AboutPageInfo(
+ new TDERadioAboutWidget(aboutData, TDERadioAboutWidget::AbtTabbed),
+ i18n("OSS Sound"),
+ i18n("OSS Sound"),
+ "tderadio_oss_sound"
+ );
+*/
+ return AboutPageInfo();
+}
+
+
+
+bool OSSSoundDevice::preparePlayback(SoundStreamID id, const TQString &channel, bool active_mode, bool start_immediately)
+{
+ if (id.isValid() && m_revPlaybackChannels.contains(channel)) {
+ m_PlaybackStreams.insert(id, SoundStreamConfig(m_revPlaybackChannels[channel], active_mode));
+ if (start_immediately)
+ startPlayback(id);
+ return true;
+ // FIXME: what to do if stream is already playing?
+ }
+ return false;
+}
+
+
+bool OSSSoundDevice::prepareCapture(SoundStreamID id, const TQString &channel)
+{
+ if (id.isValid() && m_revCaptureChannels.contains(channel)) {
+ m_CaptureStreams.insert(id, SoundStreamConfig(m_revCaptureChannels[channel]));
+ return true;
+ // FIXME: what to do if stream is already playing?
+ }
+ return false;
+}
+
+bool OSSSoundDevice::releasePlayback(SoundStreamID id)
+{
+ if (id.isValid() && m_PlaybackStreams.contains(id)) {
+ if (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id)) {
+ stopPlayback(id);
+ }
+ m_PlaybackStreams.remove(id);
+ return true;
+ }
+ return false;
+}
+
+bool OSSSoundDevice::releaseCapture(SoundStreamID id)
+{
+ if (id.isValid() && m_CaptureStreams.contains(id)) {
+ if (m_CaptureStreamID == id) {
+ stopCapture(id);
+ }
+ m_CaptureStreams.remove(id);
+ return true;
+ }
+ return false;
+}
+
+bool OSSSoundDevice::supportsPlayback() const
+{
+ return m_EnablePlayback;
+}
+
+
+bool OSSSoundDevice::supportsCapture() const
+{
+ return m_EnableCapture;
+}
+
+
+bool OSSSoundDevice::startPlayback(SoundStreamID id)
+{
+ if (id.isValid() && m_PlaybackStreams.contains(id) && m_EnablePlayback) {
+
+ SoundStreamConfig &cfg = m_PlaybackStreams[id];
+
+ bool ok = false;
+ if (cfg.m_ActiveMode) {
+ if (!m_PlaybackStreamID.isValid()) {
+ m_PlaybackStreamID = id;
+ ok = true;
+ }
+ } else {
+ if (!m_PassivePlaybackStreams.contains(id))
+ m_PassivePlaybackStreams.append(id);
+ ok = true;
+ }
+
+ if (ok) {
+ openMixerDevice();
+ if (cfg.m_Volume >= 0)
+ writeMixerVolume(cfg.m_Channel, cfg.m_Volume);
+ }
+
+ // error handling?
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool OSSSoundDevice::pausePlayback(SoundStreamID /*id*/)
+{
+ //return stopPlayback(id);
+ return false;
+}
+
+
+bool OSSSoundDevice::stopPlayback(SoundStreamID id)
+{
+ if (id.isValid() && m_PlaybackStreams.contains(id)) {
+
+ SoundStreamConfig &cfg = m_PlaybackStreams[id];
+
+ if (!cfg.m_ActiveMode) {
+ if (m_PassivePlaybackStreams.contains(id)) {
+// writeMixerVolume(cfg.m_Channel, 0);
+ m_PassivePlaybackStreams.remove(id);
+ }
+ } else if (m_PlaybackStreamID == id) {
+ m_PlaybackStreamID = SoundStreamID::InvalidID;
+ m_PlaybackBuffer.clear();
+ closeDSPDevice();
+ }
+
+ closeMixerDevice();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool OSSSoundDevice::isPlaybackRunning(SoundStreamID id, bool &b) const
+{
+ if (id.isValid() && m_PlaybackStreams.contains(id)) {
+ b = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool OSSSoundDevice::startCaptureWithFormat(SoundStreamID id,
+ const SoundFormat &proposed_format,
+ SoundFormat &real_format,
+ bool force_format)
+{
+ if (m_CaptureStreams.contains(id) && m_EnableCapture) {
+
+ if (m_CaptureStreamID != id) {
+ m_CapturePos = 0;
+ m_CaptureStartTime = time(NULL);
+ }
+
+ if (m_CaptureStreamID != id || force_format) {
+
+ m_CaptureStreamID = id;
+ SoundStreamConfig &cfg = m_CaptureStreams[id];
+
+ openMixerDevice();
+ selectCaptureChannel(cfg.m_Channel);
+ if (cfg.m_Volume >= 0)
+ writeMixerVolume(cfg.m_Channel, cfg.m_Volume);
+
+ openDSPDevice(proposed_format);
+
+ // FIXME: error handling?
+ }
+
+ real_format = m_DSPFormat;
+ m_CaptureRequestCounter++;
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool OSSSoundDevice::stopCapture(SoundStreamID id)
+{
+ if (id.isValid() && m_CaptureStreamID == id) {
+
+ if (--m_CaptureRequestCounter == 0) {
+ m_CaptureStreamID = SoundStreamID::InvalidID;
+ m_CaptureBuffer.clear();
+
+ closeMixerDevice();
+ closeDSPDevice();
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool OSSSoundDevice::isCaptureRunning(SoundStreamID id, bool &b, SoundFormat &sf) const
+{
+ if (id.isValid() && m_CaptureStreamID == id) {
+ b = true;
+ sf = m_DSPFormat;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool OSSSoundDevice::noticeSoundStreamClosed(SoundStreamID id)
+{
+ bool found = false;
+ if (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id)) {
+ stopPlayback(id);
+ found = true;
+ }
+ if (m_CaptureStreamID == id) {
+ stopCapture(id);
+ found = true;
+ }
+ m_PlaybackStreams.remove(id);
+ m_CaptureStreams.remove(id);
+ return found;
+}
+
+
+bool OSSSoundDevice::noticeSoundStreamRedirected(SoundStreamID oldID, SoundStreamID newID)
+{
+ bool found = false;
+ if (m_PlaybackStreams.contains(oldID)) {
+ m_PlaybackStreams.insert(newID, m_PlaybackStreams[oldID]);
+ if (newID != oldID)
+ m_PlaybackStreams.remove(oldID);
+ found = true;
+ }
+ if (m_CaptureStreams.contains(oldID)) {
+ m_CaptureStreams.insert(newID, m_CaptureStreams[oldID]);
+ if (newID != oldID)
+ m_CaptureStreams.remove(oldID);
+ found = true;
+ }
+
+ if (m_PlaybackStreamID == oldID)
+ m_PlaybackStreamID = newID;
+ if (m_CaptureStreamID == oldID)
+ m_CaptureStreamID = newID;
+ if (m_PassivePlaybackStreams.contains(oldID)) {
+ m_PassivePlaybackStreams.remove(oldID);
+ m_PassivePlaybackStreams.append(newID);
+ }
+ return found;
+}
+
+
+bool OSSSoundDevice::noticeSoundStreamData(SoundStreamID id,
+ const SoundFormat &format,
+ const char *data, size_t size, size_t &consumed_size,
+ const SoundMetaData &/*md*/
+ )
+{
+ if (!id.isValid() || id != m_PlaybackStreamID)
+ return false;
+
+ if (m_DSP_fd < 0) {
+ openDSPDevice(format);
+ } else if (format != m_DSPFormat) {
+ if (m_CaptureStreamID.isValid())
+ return false;
+
+ // flush playback buffer
+ size_t buffersize = 0;
+ char *buffer = m_PlaybackBuffer.getData(buffersize);
+ write(m_DSP_fd, buffer, buffersize);
+
+ // if not all could be written, it must be discarded
+ m_PlaybackBuffer.clear();
+
+ closeDSPDevice();
+ openDSPDevice(format);
+ // error handling ?
+ }
+
+ size_t n = m_PlaybackBuffer.addData(data, size);
+ consumed_size = (consumed_size == SIZE_T_DONT_CARE) ? n : min(consumed_size, n);
+
+// if (n < size) {
+// m_PlaybackSkipCount += size - n;
+// } else if (m_PlaybackSkipCount > 0) {
+// logWarning(i18n("%1: Playback buffer overflow. Skipped %1 bytes").arg(m_DSPDeviceName).arg(TQString::number(m_PlaybackSkipCount)));
+// m_PlaybackSkipCount = 0;
+// }
+
+ return true; //m_PlaybackSkipCount == 0;
+}
+
+
+
+void OSSSoundDevice::slotPoll()
+{
+ int err = 0;
+
+ if (m_CaptureStreamID.isValid() && m_DSP_fd >= 0) {
+
+ size_t bufferSize = 0;
+ char *buffer = m_CaptureBuffer.getFreeSpace(bufferSize);
+
+ int bytesRead = read(m_DSP_fd, buffer, bufferSize);
+
+ if (bytesRead > 0) {
+ m_CaptureBuffer.removeFreeSpace(bytesRead);
+ } else if (bytesRead < 0 && errno == EAGAIN) {
+ bytesRead = 0;
+ } else if (bytesRead == 0) {
+ err = -1;
+ logError(i18n("OSS device %1: No data to record").arg(m_DSPDeviceName));
+ } else {
+ err = errno;
+ }
+
+ while (m_CaptureBuffer.getFillSize() > m_CaptureBuffer.getSize() / 3) {
+ size_t size = 0;
+ buffer = m_CaptureBuffer.getData(size);
+ time_t cur_time = time(NULL);
+ size_t consumed_size = SIZE_T_DONT_CARE;
+ notifySoundStreamData(m_CaptureStreamID, m_DSPFormat, buffer, size, consumed_size, SoundMetaData(m_CapturePos, cur_time - m_CaptureStartTime, cur_time, i18n("internal stream, not stored (%1)").arg(m_DSPDeviceName)));
+ if (consumed_size == SIZE_T_DONT_CARE)
+ consumed_size = size;
+ m_CaptureBuffer.removeData(consumed_size);
+ m_CapturePos += consumed_size;
+ if (consumed_size < size)
+ break;
+ }
+ }
+
+ if (m_PlaybackStreamID.isValid()/* && m_DSP_fd >= 0*/) {
+
+ if (m_PlaybackBuffer.getFillSize() > 0 && m_DSP_fd >= 0) {
+
+ size_t buffersize = 0;
+ char *buffer = m_PlaybackBuffer.getData(buffersize);
+ int bytesWritten = write(m_DSP_fd, buffer, buffersize);
+
+ if (bytesWritten > 0) {
+ m_PlaybackBuffer.removeData(bytesWritten);
+ } else if (bytesWritten < 0 && errno == EAGAIN) {
+ bytesWritten = 0;
+ } else {
+ err = errno;
+ }
+ }
+
+ if (m_PlaybackBuffer.getFreeSize() > 0)
+ notifyReadyForPlaybackData(m_PlaybackStreamID, m_PlaybackBuffer.getFreeSize());
+ }
+
+ if (err) {
+ logError(i18n("Error %1 while handling OSS device %2").arg(TQString().setNum(err)).arg(m_DSPDeviceName));
+ }
+
+ if (m_PlaybackStreamID.isValid())
+ checkMixerVolume(m_PlaybackStreamID);
+ if (m_CaptureStreamID.isValid())
+ checkMixerVolume(m_CaptureStreamID);
+
+ TQValueListConstIterator<SoundStreamID> end = m_PassivePlaybackStreams.end();
+ for (TQValueListConstIterator<SoundStreamID> it = m_PassivePlaybackStreams.begin(); it != end; ++it)
+ checkMixerVolume(*it);
+
+}
+
+
+bool OSSSoundDevice::openDSPDevice(const SoundFormat &format, bool reopen)
+{
+ if (m_DSP_fd >= 0) {
+
+ if (reopen) {
+
+ closeDSPDevice ( /* force = */ true);
+
+ } else {
+
+ if (format != m_DSPFormat)
+ return false;
+
+ if (m_DuplexMode != DUPLEX_FULL && m_CaptureStreamID.isValid() && m_PlaybackStreamID.isValid())
+ return false;
+
+ return true;
+ }
+ } else {
+ if (reopen)
+ return true;
+ }
+
+ m_DSPFormat = format;
+
+ // first testopen for CAPS
+ m_DSP_fd = open(m_DSPDeviceName.ascii(), O_NONBLOCK | O_RDONLY);
+ bool err = m_DSP_fd < 0;
+ if (err) {
+ logError(i18n("Cannot open DSP device %1").arg(m_DSPDeviceName));
+ return false;
+ }
+ int caps = 0;
+ err |= (ioctl (m_DSP_fd, SNDCTL_DSP_GETCAPS, &caps) != 0);
+ if (err)
+ logError(i18n("Cannot read DSP capabilities for %1").arg(m_DSPDeviceName));
+
+ m_DuplexMode = (caps & DSP_CAP_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
+ close (m_DSP_fd);
+ m_DSP_fd = -1;
+
+ // opening and seeting up the device file
+ int mode = O_NONBLOCK;
+ if (m_DuplexMode == DUPLEX_FULL) {
+ mode |= O_RDWR;
+ } else if (m_CaptureStreamID.isValid()) {
+ mode |= O_RDONLY;
+ } else {
+ mode |= O_WRONLY;
+ }
+
+ m_DSP_fd = open(m_DSPDeviceName.ascii(), mode);
+
+ err = m_DSP_fd < 0;
+ if (err) {
+ logError(i18n("Cannot open DSP device %1").arg(m_DSPDeviceName));
+ return false;
+ }
+
+ int oss_format = getOSSFormat(m_DSPFormat);
+ err |= (ioctl(m_DSP_fd, SNDCTL_DSP_SETFMT, &oss_format) != 0);
+ if (err)
+ logError(i18n("Cannot set DSP sample format for %1").arg(m_DSPDeviceName));
+
+ int channels = m_DSPFormat.m_Channels;
+ err |= (ioctl(m_DSP_fd, SNDCTL_DSP_CHANNELS, &channels) != 0);
+ if (err)
+ logError(i18n("Cannot set number of channels for %1").arg(m_DSPDeviceName));
+
+ int rate = m_DSPFormat.m_SampleRate;
+ err |= (ioctl(m_DSP_fd, SNDCTL_DSP_SPEED, &rate) != 0);
+ if (err)
+ logError(i18n("Cannot set sampling rate for %1").arg(m_DSPDeviceName));
+ if (rate != (int)m_DSPFormat.m_SampleRate) {
+ logWarning(i18n("Asking for %1 Hz but %2 uses %3 Hz").
+ arg(TQString::number(m_DSPFormat.m_SampleRate)).
+ arg(m_DSPDeviceName).
+ arg(TQString::number(rate)));
+ m_DSPFormat.m_SampleRate = rate;
+ }
+
+ int stereo = m_DSPFormat.m_Channels == 2;
+ err |= (ioctl(m_DSP_fd, SNDCTL_DSP_STEREO, &stereo) != 0);
+ if (err)
+ logError(i18n("Cannot set stereo mode for %1").arg(m_DSPDeviceName));
+
+ unsigned sampleSize = m_DSPFormat.m_SampleBits;
+ err |= (ioctl(m_DSP_fd, SNDCTL_DSP_SAMPLESIZE, &sampleSize) != 0);
+ if (err || sampleSize != m_DSPFormat.m_SampleBits)
+ logError(i18n("Cannot set sample size for %1").arg(m_DSPDeviceName));
+
+ // setup buffer, ask for 40ms latency
+ int tmp = (400 * m_DSPFormat.frameSize() * m_DSPFormat.m_SampleRate) / 1000;
+ int mask = -1; for (; tmp; tmp >>= 1) ++mask;
+ if (mask < 8) mask = 12; // default 4kB
+ mask |= 0x7FFF0000;
+ err |= ioctl (m_DSP_fd, SNDCTL_DSP_SETFRAGMENT, &mask);
+ if (err)
+ logError(i18n("Cannot set buffers for %1").arg(m_DSPDeviceName));
+
+ int bufferBlockSize = 0;
+ err |= ioctl (m_DSP_fd, SNDCTL_DSP_GETBLKSIZE, &bufferBlockSize);
+ if (err) {
+ logError(i18n("Cannot read buffer size for %1").arg(m_DSPDeviceName));
+ } else {
+ logInfo(i18n("%1 uses buffer blocks of %2 bytes").arg(m_DSPDeviceName).arg(TQString::number(bufferBlockSize)));
+ size_t tmp = (((m_BufferSize - 1) / bufferBlockSize) + 1) * bufferBlockSize;
+ setBufferSize(tmp);
+ logInfo(i18n("adjusted own buffer size to %1 bytes").arg(TQString::number(tmp)));
+ }
+
+ int trigger = ~PCM_ENABLE_INPUT & ~PCM_ENABLE_OUTPUT;
+ ioctl(m_DSP_fd, SNDCTL_DSP_SETTRIGGER, &trigger);
+ trigger = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
+ ioctl(m_DSP_fd, SNDCTL_DSP_SETTRIGGER, &trigger);
+
+ if (!err) {
+ m_PollingTimer.start(40);
+ } else {
+ closeDSPDevice();
+ }
+
+ m_CaptureSkipCount = 0;
+ //m_PlaybackSkipCount = 0;
+
+ return !err;
+}
+
+
+bool OSSSoundDevice::closeDSPDevice(bool force)
+{
+ if ((!m_PlaybackStreamID.isValid() && !m_CaptureStreamID.isValid()) || force) {
+
+ if (m_Mixer_fd < 0)
+ m_PollingTimer.stop();
+
+ if (m_DSP_fd >= 0)
+ close (m_DSP_fd);
+ m_DSP_fd = -1;
+
+ m_PlaybackBuffer.clear();
+ m_CaptureBuffer.clear();
+ }
+ return true;
+}
+
+
+bool OSSSoundDevice::openMixerDevice(bool reopen)
+{
+ if (reopen) {
+ if (m_Mixer_fd >= 0)
+ closeMixerDevice(/* force = */ true);
+ else
+ return true;
+ }
+
+ if (m_Mixer_fd < 0)
+ m_Mixer_fd = open(m_MixerDeviceName.ascii(), O_RDONLY);
+
+ if (m_Mixer_fd < 0) {
+ logError(i18n("Cannot open mixer device %1").arg(m_MixerDeviceName));
+ } else {
+ m_PollingTimer.start(40);
+ }
+ return m_Mixer_fd >= 0;
+}
+
+
+bool OSSSoundDevice::closeMixerDevice(bool force)
+{
+ if ((!m_PlaybackStreamID.isValid() && !m_CaptureStreamID.isValid()) || force) {
+
+ if (m_DSP_fd < 0)
+ m_PollingTimer.stop();
+
+ if (m_Mixer_fd >= 0)
+ close (m_Mixer_fd);
+ m_Mixer_fd = -1;
+ }
+ return m_Mixer_fd < 0;
+}
+
+
+void OSSSoundDevice::getMixerChannels(int query, TQStringList &retval, TQMap<TQString, int> &revmap) const
+{
+ retval.clear();
+ revmap.clear();
+
+ int fd = m_Mixer_fd;
+ if (fd < 0)
+ fd = open(m_MixerDeviceName.ascii(), O_RDONLY);
+
+ if (fd < 0) {
+ logError(i18n("OSSSoundDevice::getMixerChannels: Cannot open mixer device %1").arg(m_MixerDeviceName));
+ }
+
+ if (fd >= 0) {
+ int mask = 0;
+ if ( ioctl(fd, MIXER_READ(query), &mask) == 0 ) {
+ for (int i = 0; i < SOUND_MIXER_NRDEVICES; ++i) {
+ if (mask & (1 << i)) {
+ static const char *labels[] = SOUND_DEVICE_LABELS;
+ retval.append(i18n(labels[i]));
+ revmap.insert(i18n(labels[i]), i);
+ }
+ }
+ } else {
+ logError(i18n("OSSSoundDevice::getMixerChannels: Cannot read mixer device mask on device %1").arg(m_MixerDeviceName));
+ }
+ }
+ if (fd != m_Mixer_fd)
+ close(fd);
+}
+
+
+const TQStringList &OSSSoundDevice::getPlaybackChannels() const
+{
+ return m_PlaybackChannels;
+}
+
+
+const TQStringList &OSSSoundDevice::getCaptureChannels() const
+{
+ return m_CaptureChannels;
+}
+
+
+bool OSSSoundDevice::setPlaybackVolume(SoundStreamID id, float volume)
+{
+ if (id.isValid() && (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id))) {
+ SoundStreamConfig &cfg = m_PlaybackStreams[id];
+
+ if (rint(100*volume) != rint(100*cfg.m_Volume)) {
+ cfg.m_Volume = writeMixerVolume(cfg.m_Channel, volume);
+ notifyPlaybackVolumeChanged(id, cfg.m_Volume);
+ }
+ return true;
+ }
+ return false;
+}
+
+
+bool OSSSoundDevice::setCaptureVolume(SoundStreamID id, float volume)
+{
+ if (id.isValid() && m_CaptureStreamID == id) {
+ SoundStreamConfig &cfg = m_CaptureStreams[id];
+
+ if (rint(100*volume) != rint(100*cfg.m_Volume)) {
+ cfg.m_Volume = writeMixerVolume(cfg.m_Channel, volume);
+ notifyCaptureVolumeChanged(id, cfg.m_Volume);
+ }
+ return true;
+ }
+ return false;
+}
+
+
+bool OSSSoundDevice::getPlaybackVolume(SoundStreamID id, float &volume) const
+{
+ if (id.isValid() && (m_PlaybackStreamID == id || m_PassivePlaybackStreams.contains(id))) {
+ const SoundStreamConfig &cfg = m_PlaybackStreams[id];
+ volume = cfg.m_Volume;
+ return true;
+ }
+ return false;
+}
+
+
+bool OSSSoundDevice::getCaptureVolume(SoundStreamID id, float &volume) const
+{
+ if (id.isValid() && m_CaptureStreamID == id) {
+ const SoundStreamConfig &cfg = m_CaptureStreams[id];
+ volume = cfg.m_Volume;
+ return true;
+ }
+ return false;
+}
+
+
+void OSSSoundDevice::checkMixerVolume(SoundStreamID id)
+{
+ if (m_Mixer_fd >= 0 && id.isValid()) {
+
+ if (m_PassivePlaybackStreams.contains(id) || m_PlaybackStreamID == id) {
+ SoundStreamConfig &cfg = m_PlaybackStreams[id];
+
+ float v = readMixerVolume(cfg.m_Channel);
+ if (rint(100*cfg.m_Volume) != rint(100*v)) {
+ cfg.m_Volume = v;
+ notifyPlaybackVolumeChanged(id, v);
+ }
+ }
+
+ if (m_CaptureStreamID == id) {
+ SoundStreamConfig &cfg = m_CaptureStreams[id];
+
+ float v = readMixerVolume(cfg.m_Channel);
+ if (rint(100*cfg.m_Volume) != rint(100*v)) {
+ cfg.m_Volume = v;
+ notifyCaptureVolumeChanged(id, v);
+ }
+ }
+ }
+}
+
+
+float OSSSoundDevice::readMixerVolume(int channel) const
+{
+ _lrvol tmpvol;
+ int err = ioctl(m_Mixer_fd, MIXER_READ(channel), &tmpvol);
+ if (err) {
+ logError("OSSSound::readMixerVolume: " +
+ i18n("error %1 while reading volume from %2")
+ .arg(TQString().setNum(err))
+ .arg(m_MixerDeviceName));
+ tmpvol.l = tmpvol.r = 0;
+ }
+ return float(tmpvol.l) / 100.0;
+}
+
+
+float OSSSoundDevice::writeMixerVolume (int channel, float vol)
+{
+ if (vol > 1.0) vol = 1.0;
+ if (vol < 0) vol = 0.0;
+
+ const int divs = 100;
+ vol = rint(vol * divs) / float(divs);
+
+ if (m_Mixer_fd >= 0) {
+ _lrvol tmpvol;
+ tmpvol.r = tmpvol.l = (unsigned int)(rint(vol * divs));
+ int err = ioctl(m_Mixer_fd, MIXER_WRITE(channel), &tmpvol);
+ if (err != 0) {
+ logError("OSSSoundDevice::writeMixerVolume: " +
+ i18n("error %1 while setting volume to %2 on device %3")
+ .arg(TQString().setNum(err))
+ .arg(TQString().setNum(vol))
+ .arg(m_MixerDeviceName));
+ return -1;
+ }
+ }
+ return vol;
+}
+
+
+void OSSSoundDevice::selectCaptureChannel (int channel)
+{
+ int x = 1 << channel;
+ int err = ioctl(m_Mixer_fd, SOUND_MIXER_WRITE_RECSRC, &x);
+ if (err)
+ logError(i18n("Selecting recording source on device %1 failed with error code %2")
+ .arg(m_MixerDeviceName)
+ .arg(TQString::number(err)));
+ _lrvol tmpvol;
+ err = ioctl(m_Mixer_fd, MIXER_READ(SOUND_MIXER_IGAIN), &tmpvol);
+ if (err)
+ logError(i18n("Reading igain volume on device %1 failed with error code %2")
+ .arg(m_MixerDeviceName)
+ .arg(TQString::number(err)));
+ if (tmpvol.r == 0 && tmpvol.l == 0) {
+ tmpvol.r = tmpvol.l = 1;
+ err = ioctl(m_Mixer_fd, MIXER_WRITE(SOUND_MIXER_IGAIN), &tmpvol);
+ if (err)
+ logError(i18n("Setting igain volume on device %1 failed with error code %2")
+ .arg(m_MixerDeviceName)
+ .arg(TQString::number(err)));
+ }
+}
+
+
+int OSSSoundDevice::getOSSFormat(const SoundFormat &f)
+{
+ if (f.m_SampleBits == 16) {
+ switch (2 * f.m_IsSigned + (f.m_Endianess == LITTLE_ENDIAN)) {
+ case 0: return AFMT_U16_BE;
+ case 1: return AFMT_U16_LE;
+ case 2: return AFMT_S16_BE;
+ case 3: return AFMT_S16_LE;
+ }
+ }
+ if (f.m_SampleBits == 8) {
+ switch (f.m_IsSigned) {
+ case 0: return AFMT_U8;
+ case 1: return AFMT_S8;
+ }
+ }
+ return 0;
+}
+
+
+void OSSSoundDevice::setBufferSize(int s)
+{
+ m_BufferSize = s;
+ m_PlaybackBuffer.resize(m_BufferSize);
+ m_CaptureBuffer.resize(m_BufferSize);
+}
+
+
+void OSSSoundDevice::enablePlayback(bool on)
+{
+ m_EnablePlayback = on;
+}
+
+
+void OSSSoundDevice::enableCapture(bool on)
+{
+ m_EnableCapture = on;
+}
+
+
+void OSSSoundDevice::setDSPDeviceName(const TQString &s)
+{
+ m_DSPDeviceName = s;
+ SoundFormat f = m_DSPFormat;
+ if (m_DSP_fd >= 0)
+ openDSPDevice(f, /* reopen = */ true);
+}
+
+
+TQString OSSSoundDevice::getSoundStreamClientDescription() const
+{
+ return i18n("OSS Sound Device %1").arg(PluginBase::name());
+}
+
+
+
+#include "oss-sound.moc"