mumble-voip_mumble/src/mumble/OSS.cpp

353 lines
8.6 KiB
C++

// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "OSS.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include "MainWindow.h"
#include "User.h"
#include "Global.h"
#include <memory>
#include <vector>
#define NBLOCKS 8
class OSSEnumerator {
public:
QHash< QString, QString > qhInput;
QHash< QString, QString > qhOutput;
QHash< QString, QString > qhDevices;
OSSEnumerator();
};
static OSSEnumerator *cards = nullptr;
class OSSInit : public DeferInit {
void initialize() { cards = new OSSEnumerator(); };
void destroy() {
delete cards;
cards = nullptr;
};
};
static OSSInit ossi;
class OSSInputRegistrar : public AudioInputRegistrar {
public:
OSSInputRegistrar();
virtual AudioInput *create();
virtual const QVariant getDeviceChoice();
virtual const QList< audioDevice > getDeviceChoices();
virtual void setDeviceChoice(const QVariant &, Settings &);
virtual bool canEcho(EchoCancelOptionID echoCancelID, const QString &outputSystem) const;
virtual bool isMicrophoneAccessDeniedByOS() { return false; };
};
class OSSOutputRegistrar : public AudioOutputRegistrar {
public:
OSSOutputRegistrar();
virtual AudioOutput *create();
virtual const QVariant getDeviceChoice();
virtual const QList< audioDevice > getDeviceChoices();
virtual void setDeviceChoice(const QVariant &, Settings &);
};
static OSSInputRegistrar airOSS;
static OSSOutputRegistrar aorOSS;
OSSInputRegistrar::OSSInputRegistrar() : AudioInputRegistrar(QLatin1String("OSS")) {
}
AudioInput *OSSInputRegistrar::create() {
return new OSSInput();
}
const QVariant OSSInputRegistrar::getDeviceChoice() {
return Global::get().s.qsOSSInput;
}
const QList< audioDevice > OSSInputRegistrar::getDeviceChoices() {
QList< audioDevice > choices;
QStringList keys = cards->qhInput.keys();
std::sort(keys.begin(), keys.end());
for (const auto &key : keys) {
choices << audioDevice(cards->qhInput.value(key), key);
}
return choices;
}
void OSSInputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
s.qsOSSInput = choice.toString();
}
bool OSSInputRegistrar::canEcho(EchoCancelOptionID, const QString &) const {
return false;
}
OSSOutputRegistrar::OSSOutputRegistrar() : AudioOutputRegistrar(QLatin1String("OSS")) {
}
AudioOutput *OSSOutputRegistrar::create() {
return new OSSOutput();
}
const QVariant OSSOutputRegistrar::getDeviceChoice() {
return Global::get().s.qsOSSOutput;
}
const QList< audioDevice > OSSOutputRegistrar::getDeviceChoices() {
QList< audioDevice > choices;
QStringList keys = cards->qhOutput.keys();
std::sort(keys.begin(), keys.end());
for (const auto &key : keys) {
choices << audioDevice(cards->qhOutput.value(key), key);
}
return choices;
}
void OSSOutputRegistrar::setDeviceChoice(const QVariant &choice, Settings &s) {
s.qsOSSOutput = choice.toString();
}
OSSEnumerator::OSSEnumerator() {
qhInput.insert(QString(), QLatin1String("Default OSS Device"));
qhOutput.insert(QString(), QLatin1String("Default OSS Device"));
qhDevices.insert(QString(), QLatin1String("/dev/dsp"));
#if (SOUND_VERSION >= 0x040002)
int mixerfd = open("/dev/mixer", O_RDWR, 0);
if (mixerfd == -1) {
qWarning("OSSEnumerator: Failed to open /dev/mixer");
return;
}
oss_sysinfo sysinfo;
if (ioctl(mixerfd, SNDCTL_SYSINFO, &sysinfo) == -1) {
qWarning("OSSEnumerator: Failed SNDCTL_SYSINFO");
return;
}
for (int i = 0; i < sysinfo.numaudios; i++) {
oss_audioinfo ainfo;
ainfo.dev = i;
if (ioctl(mixerfd, SNDCTL_AUDIOINFO, &ainfo) == -1) {
qWarning("OSSEnumerator: SNDCTL_AUDIOINFO failed for device %d", i);
continue;
}
QString handle = QLatin1String(ainfo.handle);
QString name = QLatin1String(ainfo.name);
QString device = QLatin1String(ainfo.devnode);
if (ainfo.caps & PCM_CAP_HIDDEN)
continue;
qhDevices.insert(handle, device);
if (ainfo.caps & PCM_CAP_INPUT)
qhInput.insert(handle, name);
if (ainfo.caps & PCM_CAP_OUTPUT)
qhOutput.insert(handle, name);
}
close(mixerfd);
#endif
}
OSSInput::OSSInput() {
bRunning = true;
}
OSSInput::~OSSInput() {
// Signal input thread to end
bRunning = false;
wait();
}
class FileDescriptor {
public:
FileDescriptor(int fd = -1) : m_fd(fd) {}
~FileDescriptor() {
if (m_fd != -1) {
close(m_fd);
m_fd = -1;
}
}
operator int() const { return m_fd; }
private:
int m_fd = -1;
};
void OSSInput::run() {
QByteArray device = cards->qhDevices.value(Global::get().s.qsOSSInput).toLatin1();
if (device.isEmpty()) {
qWarning("OSSInput: Stored device not found, falling back to default");
device = cards->qhDevices.value(QString()).toLatin1();
}
FileDescriptor fd(open(device.constData(), O_RDONLY, 0));
if (fd == -1) {
qWarning("OSSInput: Failed to open %s", device.constData());
return;
}
int ival;
ival = AFMT_S16_NE;
if ((ioctl(fd, SNDCTL_DSP_SETFMT, &ival) == -1) || (ival != AFMT_S16_NE)) {
qWarning("OSSInput: Failed to set sound format");
return;
}
ival = 1;
if ((ioctl(fd, SNDCTL_DSP_CHANNELS, &ival) == -1)) {
qWarning("OSSInput: Failed to set mono mode");
return;
}
iMicChannels = static_cast< unsigned int >(ival);
ival = SAMPLE_RATE;
if (ioctl(fd, SNDCTL_DSP_SPEED, &ival) == -1) {
qWarning("OSSInput: Failed to set speed");
return;
}
iMicFreq = static_cast< unsigned int >(ival);
qWarning("OSSInput: Starting audio capture from %s", device.constData());
eMicFormat = SampleShort;
initializeMixer();
std::vector< short > buffer;
buffer.resize(iMicLength);
while (bRunning) {
std::size_t len = iMicLength * iMicChannels * sizeof(short);
ssize_t l = read(fd, buffer.data(), len);
if (l != static_cast< ssize_t >(len)) {
qWarning("OSSInput: Read %zd", l);
break;
}
addMic(buffer.data(), iMicLength);
}
qWarning("OSSInput: Releasing.");
ioctl(fd, SNDCTL_DSP_RESET, nullptr);
}
OSSOutput::OSSOutput() {
bRunning = true;
qWarning("OSSOutput: Initialized");
}
OSSOutput::~OSSOutput() {
bRunning = false;
// Call destructor of all children
wipe();
// Wait for terminate
wait();
qWarning("OSSOutput: Destroyed");
}
void OSSOutput::run() {
QByteArray device = cards->qhDevices.value(Global::get().s.qsOSSOutput).toLatin1();
if (device.isEmpty()) {
qWarning("OSSOutput: Stored device not found, falling back to default");
device = cards->qhDevices.value(QString()).toLatin1();
}
int fd = open(device.constData(), O_WRONLY, 0);
if (fd == -1) {
qWarning("OSSOutput: Failed to open %s", device.constData());
return;
}
int ival;
ival = (Global::get().s.iOutputDelay + 1) << 16 | 11;
if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ival) == -1) {
qWarning("OSSOutput: Failed to set fragment");
}
ival = AFMT_S16_NE;
if ((ioctl(fd, SNDCTL_DSP_SETFMT, &ival) == -1) || (ival != AFMT_S16_NE)) {
qWarning("OSSOutput: Failed to set sound format");
if ((ival != AFMT_S16_NE))
close(fd);
return;
}
iChannels = 2;
ival = static_cast< int >(iChannels);
if ((ioctl(fd, SNDCTL_DSP_CHANNELS, &ival) == -1) && (ival == static_cast< int >(iChannels))) {
qWarning("OSSOutput: Failed to set channels");
return;
}
iChannels = static_cast< unsigned int >(ival);
ival = SAMPLE_RATE;
if (ioctl(fd, SNDCTL_DSP_SPEED, &ival) == -1) {
qWarning("OSSOutput: Failed to set speed");
return;
}
iMixerFreq = static_cast< unsigned int >(ival);
const unsigned int chanmasks[32] = { SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, SPEAKER_FRONT_CENTER,
SPEAKER_LOW_FREQUENCY, SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT,
SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, SPEAKER_BACK_CENTER };
eSampleFormat = SampleShort;
initializeMixer(chanmasks);
unsigned int iOutputBlock = (iFrameSize * iMixerFreq) / SAMPLE_RATE;
qWarning("OSSOutput: Starting audio playback to %s", device.constData());
std::size_t blocklen = iOutputBlock * iChannels * sizeof(short);
static std::vector< short > mbuffer;
mbuffer.resize(iOutputBlock * iChannels);
while (bRunning) {
bool stillRun = mix(mbuffer.data(), iOutputBlock);
if (stillRun) {
ssize_t l = write(fd, mbuffer.data(), blocklen);
if (l != static_cast< ssize_t >(blocklen)) {
qWarning("OSSOutput: Write %zd != %zd", l, blocklen);
break;
}
} else {
while (!mix(mbuffer.data(), iOutputBlock) && bRunning)
this->msleep(20);
ssize_t l = write(fd, mbuffer.data(), blocklen);
if (l != static_cast< ssize_t >(blocklen)) {
qWarning("OSSOutput: Write %zd != %zd", l, blocklen);
break;
}
}
}
qWarning("OSSOutput: Releasing device");
ioctl(fd, SNDCTL_DSP_RESET, nullptr);
close(fd);
}
#undef NBLOCKS