You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
992 lines
29 KiB
992 lines
29 KiB
15 years ago
|
/***************************************************************************
|
||
|
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 <klocale.h>
|
||
|
#include <kaboutdata.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, "kradio-oss-sound", i18n("Open Sound System (OSS) Support"));
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
struct _lrvol { unsigned char l, r; short dummy; };
|
||
|
|
||
|
OSSSoundDevice::OSSSoundDevice(const QString &name)
|
||
|
: QObject(NULL, NULL),
|
||
|
PluginBase(name, i18n("KRadio 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)
|
||
|
{
|
||
|
QObject::connect(&m_PollingTimer, SIGNAL(timeout()), this, 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 (KConfig *c) const
|
||
|
{
|
||
|
c->setGroup(QString("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 (KConfig *c)
|
||
|
{
|
||
|
c->setGroup(QString("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 QString &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);
|
||
|
QObject::connect(this, SIGNAL(sigUpdateConfig()), conf, SLOT(slotUpdateConfig()));
|
||
|
return ConfigPageInfo (conf,
|
||
|
i18n("OSS Sound"),
|
||
|
i18n("OSS Sound Device Options"),
|
||
|
"kradio_oss");
|
||
|
}
|
||
|
|
||
|
|
||
|
AboutPageInfo OSSSoundDevice::createAboutPage()
|
||
|
{
|
||
|
/* KAboutData aboutData("kradio",
|
||
|
NULL,
|
||
|
NULL,
|
||
|
I18N_NOOP("OSS Sound Plugin for KRadio"),
|
||
|
KAboutData::License_GPL,
|
||
|
"(c) 2004 Martin Witte",
|
||
|
0,
|
||
|
"http://sourceforge.net/projects/kradio",
|
||
|
0);
|
||
|
aboutData.addAuthor("Martin Witte", "", "witte@kawo1.rwth-aachen.de");
|
||
|
|
||
|
return AboutPageInfo(
|
||
|
new KRadioAboutWidget(aboutData, KRadioAboutWidget::AbtTabbed),
|
||
|
i18n("OSS Sound"),
|
||
|
i18n("OSS Sound"),
|
||
|
"kradio_oss_sound"
|
||
|
);
|
||
|
*/
|
||
|
return AboutPageInfo();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
bool OSSSoundDevice::preparePlayback(SoundStreamID id, const QString &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 QString &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(QString::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(QString().setNum(err)).arg(m_DSPDeviceName));
|
||
|
}
|
||
|
|
||
|
if (m_PlaybackStreamID.isValid())
|
||
|
checkMixerVolume(m_PlaybackStreamID);
|
||
|
if (m_CaptureStreamID.isValid())
|
||
|
checkMixerVolume(m_CaptureStreamID);
|
||
|
|
||
|
QValueListConstIterator<SoundStreamID> end = m_PassivePlaybackStreams.end();
|
||
|
for (QValueListConstIterator<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(QString::number(m_DSPFormat.m_SampleRate)).
|
||
|
arg(m_DSPDeviceName).
|
||
|
arg(QString::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(QString::number(bufferBlockSize)));
|
||
|
size_t tmp = (((m_BufferSize - 1) / bufferBlockSize) + 1) * bufferBlockSize;
|
||
|
setBufferSize(tmp);
|
||
|
logInfo(i18n("adjusted own buffer size to %1 bytes").arg(QString::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, QStringList &retval, QMap<QString, 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 QStringList &OSSSoundDevice::getPlaybackChannels() const
|
||
|
{
|
||
|
return m_PlaybackChannels;
|
||
|
}
|
||
|
|
||
|
|
||
|
const QStringList &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(QString().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(QString().setNum(err))
|
||
|
.arg(QString().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(QString::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(QString::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(QString::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 QString &s)
|
||
|
{
|
||
|
m_DSPDeviceName = s;
|
||
|
SoundFormat f = m_DSPFormat;
|
||
|
if (m_DSP_fd >= 0)
|
||
|
openDSPDevice(f, /* reopen = */ true);
|
||
|
}
|
||
|
|
||
|
|
||
|
QString OSSSoundDevice::getSoundStreamClientDescription() const
|
||
|
{
|
||
|
return i18n("OSS Sound Device %1").arg(PluginBase::name());
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#include "oss-sound.moc"
|