/* 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(uLawDecode(buffer.at(f)), data + (f * 2)); else qToLittleEndian((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(data + f * 2))); } } else if (radioSampleBits == 8) { for (int f = 0; f < len / 2; f++) { buffer.append((quint8)(((qFromLittleEndian(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; }