wfview/audiohandler.cpp

419 wiersze
13 KiB
C++

/*
This class handles both RX and TX audio, each is created as a seperate instance of the class
but as the setup/handling if output (RX) and input (TX) devices is so similar I have combined them.
*/
#include "audiohandler.h"
#include "logcategories.h"
#define MULAW_BIAS 33
#define MULAW_MAX 0x1fff
qint8 uLawEncode(qint16 number)
{
quint16 mask = 0x1000;
quint8 sign = 0;
quint8 position = 12;
quint8 lsb = 0;
if (number < 0)
{
number = -number;
sign = 0x80;
}
number += MULAW_BIAS;
if (number > MULAW_MAX)
{
number = MULAW_MAX;
}
for (; ((number & mask) != mask && position >= 5); mask >>= 1, position--)
;
lsb = (number >> (position - 4)) & 0x0f;
return (~(sign | ((position - 5) << 4) | lsb));
}
/*
qint16 uLawDecode(qint8 number)
{
quint8 sign = 0, position = 0;
qint16 decoded = 0;
number = ~number;
if (number & 0x80)
{
number &= ~(1 << 7);
sign = -1;
}
position = ((number & 0xF0) >> 4) + 5;
decoded = ((1 << position) | ((number & 0x0F) << (position - 4))
| (1 << (position - 5))) - MULAW_BIAS;
return (sign == 0) ? (decoded) : (-(decoded));
}
*/
qint16 uLawDecode(const quint8 in)
{
static const qint16 ulaw_decode[256] = {
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0 };
return ulaw_decode[in];
}
audioHandler::audioHandler(QObject* parent) :
QIODevice(parent),
isInitialized(false),
audioOutput(Q_NULLPTR),
audioInput(Q_NULLPTR),
isUlaw(false),
bufferSize(0),
isInput(0),
volume(1.0f)
{
}
audioHandler::~audioHandler()
{
stop();
if (audioOutput != Q_NULLPTR) {
delete audioOutput;
}
if (audioInput != Q_NULLPTR) {
delete audioInput;
}
}
bool audioHandler::init(const quint8 bits, const quint8 channels, const quint16 samplerate, const quint16 buffer, const bool ulaw, const bool isinput)
{
if (isInitialized) {
return false;
}
/* Always use 16 bit 48K samples internally*/
format.setSampleSize(16);
format.setChannelCount(channels);
format.setSampleRate(48000);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
this->bufferSize = buffer;
this->isUlaw = ulaw;
this->isInput = isinput;
this->radioSampleBits = bits;
this->radioSampleRate = samplerate;
if (isInput)
isInitialized = setDevice(QAudioDeviceInfo::defaultInputDevice());
else
isInitialized = setDevice(QAudioDeviceInfo::defaultOutputDevice());
return isInitialized;
}
bool audioHandler::setDevice(QAudioDeviceInfo deviceInfo)
{
qDebug(logAudio()) << this->metaObject()->className() << ": setDevice() running :" << deviceInfo.deviceName();
if (!deviceInfo.isFormatSupported(format)) {
if (deviceInfo.isNull())
{
qDebug(logAudio()) << "No audio device was found. You probably need to install libqt5multimedia-plugins.";
}
else {
qDebug(logAudio()) << "Audio Devices found: ";
const auto deviceInfos = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
for (const QAudioDeviceInfo& deviceInfo : deviceInfos)
{
qDebug(logAudio()) << "Device name: " << deviceInfo.deviceName();
qDebug(logAudio()) << "is null (probably not good):" << deviceInfo.isNull();
qDebug(logAudio()) << "channel count:" << deviceInfo.supportedChannelCounts();
qDebug(logAudio()) << "byte order:" << deviceInfo.supportedByteOrders();
qDebug(logAudio()) << "supported codecs:" << deviceInfo.supportedCodecs();
qDebug(logAudio()) << "sample rates:" << deviceInfo.supportedSampleRates();
qDebug(logAudio()) << "sample sizes:" << deviceInfo.supportedSampleSizes();
qDebug(logAudio()) << "sample types:" << deviceInfo.supportedSampleTypes();
}
qDebug(logAudio()) << "----- done with audio info -----";
}
qDebug(logAudio()) << "Format not supported, choosing nearest supported format - which may not work!";
deviceInfo.nearestFormat(format);
}
this->deviceInfo = deviceInfo;
this->reinit();
return true;
}
void audioHandler::reinit()
{
qDebug(logAudio()) << this->metaObject()->className() << ": reinit() running";
if (audioOutput && audioOutput->state() != QAudio::StoppedState) {
this->stop();
}
// Calculate the minimum required audio buffer
// This may need work depending on how it performs on other platforms.
//int audioBuffer = format.sampleRate() / 10;
//audioBuffer = audioBuffer * (format.sampleSize() / 8);
if (this->isInput)
{
// (Re)initialize audio input
if (audioInput != Q_NULLPTR)
delete audioInput;
audioInput = new QAudioInput(deviceInfo, format, this);
//audioInput->setBufferSize(audioBuffer);
//audioInput->setNotifyInterval(20);
connect(audioInput, SIGNAL(notify()), SLOT(notified()));
connect(audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
}
else {
// (Re)initialize audio output
if (audioOutput != Q_NULLPTR)
delete audioOutput;
audioOutput = Q_NULLPTR;
audioOutput = new QAudioOutput(deviceInfo, format, this);
//audioOutput->setBufferSize(audioBuffer);
connect(audioOutput, SIGNAL(notify()), SLOT(notified()));
connect(audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
}
this->start();
this->flush();
}
void audioHandler::start()
{
qDebug(logAudio()) << this->metaObject()->className() << ": start() running";
if ((audioOutput == Q_NULLPTR || audioOutput->state() != QAudio::StoppedState) &&
(audioInput == Q_NULLPTR || audioInput->state() != QAudio::StoppedState) ) {
return;
}
if (isInput) {
this->open(QIODevice::WriteOnly | QIODevice::Unbuffered);
audioInput->start(this);
}
else {
this->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
audioOutput->start(this);
}
}
void audioHandler::setVolume(float volume)
{
volume = volume;
}
void audioHandler::flush()
{
qDebug(logAudio()) << this->metaObject()->className() << ": flush() running";
this->stop();
if (isInput) {
audioInput->reset();
}
else {
audioOutput->reset();
}
buffer.clear();
this->start();
}
void audioHandler::stop()
{
QMutexLocker locker(&mutex);
if (audioOutput && audioOutput->state() != QAudio::StoppedState) {
// Stop audio output
audioOutput->stop();
QByteArray ret;
buffer.clear();
this->close();
}
if (audioInput && audioInput->state() != QAudio::StoppedState) {
// Stop audio output
audioInput->stop();
buffer.clear();
this->close();
}
}
qint64 audioHandler::readData(char* data, qint64 maxlen)
{
// Calculate output length, always full samples
int outlen = 0;
if (radioSampleBits == 8)
{
// Input buffer is 8bit and output buffer is 16bit
outlen = qMin(buffer.length(), (int)maxlen / 2);
for (quint16 f = 0; f < outlen; f++)
{
if (isUlaw)
qToLittleEndian<qint16>(uLawDecode(buffer.at(f)), data + (f * 2));
else
qToLittleEndian<qint16>((qint16)(buffer[f] << 8) - 32640, data + (f * 2));
}
QMutexLocker locker(&mutex);
buffer.remove(0, outlen);
outlen = outlen * 2;
}
else if (radioSampleBits == 16) {
// Just copy it
outlen = qMin(buffer.length(), (int)maxlen);
if (outlen % 2 != 0) {
outlen += 1; // Not sure if this is necessary as we should always have an even number!
}
memcpy(data, buffer.data(), outlen);
QMutexLocker locker(&mutex);
buffer.remove(0, outlen);
}
else {
qDebug(logAudio()) << "Sample bits MUST be 8 or 16 - got: " << radioSampleBits; // Should never happen?
}
return outlen;
}
qint64 audioHandler::writeData(const char* data, qint64 len)
{
QMutexLocker locker(&mutex);
if (buffer.length() > bufferSize * 4)
{
qWarning() << "writeData() Buffer overflow";
// buffer.clear(); // Will cause a click!
}
if (isUlaw) {
for (int f = 0; f < len / 2; f++)
{
buffer.append((quint8)uLawEncode(qFromLittleEndian<qint16>(data + f * 2)));
}
}
else if (radioSampleBits == 8) {
for (int f = 0; f < len / 2; f++)
{
buffer.append((quint8)(((qFromLittleEndian<qint16>(data + f * 2) >> 8) ^ 0x80) & 0xff));
}
}
else if (radioSampleBits == 16) {
buffer.append(QByteArray::fromRawData(data, len));
}
else {
qWarning() << "Unsupported number of bits! :" << radioSampleBits;
}
if (buffer.size() >= radioSampleBits * 120) {
chunkAvailable = true;
}
return (len); // Always return the same number as we received
}
qint64 audioHandler::bytesAvailable() const
{
return buffer.length() + QIODevice::bytesAvailable();
}
bool audioHandler::isSequential() const
{
return true;
}
void audioHandler::notified()
{
}
void audioHandler::stateChanged(QAudio::State state)
{
if (state == QAudio::IdleState && audioOutput->error() == QAudio::UnderrunError) {
qDebug(logAudio()) << this->metaObject()->className() << "RX:Buffer underrun";
//if (buffer.length() < bufferSize) {
// audioOutput->suspend();
//}
}
//qDebug(logAudio()) << this->metaObject()->className() << ": state = " << state;
}
void audioHandler::incomingAudio(const QByteArray& data)
{
//qDebug(logAudio()) << "Got " << data.length() << " samples";
if (audioOutput != Q_NULLPTR && audioOutput->state() != QAudio::StoppedState) {
QMutexLocker locker(&mutex);
buffer.append(data);
if (audioOutput->state() == QAudio::SuspendedState) {
qDebug(logAudio()) << "RX Audio Suspended, Resuming...";
audioOutput->resume();
}
}
}
void audioHandler::changeBufferSize(const quint16 newSize)
{
QMutexLocker locker(&mutex);
qDebug(logAudio()) << this->metaObject()->className() << ": Changing buffer size to: " << newSize << " from " << bufferSize;
bufferSize = newSize;
}
void audioHandler::getBufferSize()
{
emit sendBufferSize(audioOutput->bufferSize());
}
bool audioHandler::isChunkAvailable()
{
return chunkAvailable;
}
void audioHandler::getNextAudioChunk(QByteArray& ret)
{
quint16 numSamples = radioSampleBits * 120;
if (buffer.size() >= numSamples) {
QMutexLocker locker(&mutex);
ret.append(buffer.mid(0, numSamples));
buffer.remove(0, numSamples);
}
if (buffer.size() < numSamples)
{
chunkAvailable = false;
}
return;
}