kopia lustrzana https://gitlab.com/eliggett/wfview
344 wiersze
9.6 KiB
C++
344 wiersze
9.6 KiB
C++
#include "pahandler.h"
|
|
|
|
#include "logcategories.h"
|
|
|
|
#if defined(Q_OS_WIN)
|
|
#include <objbase.h>
|
|
#endif
|
|
|
|
|
|
paHandler::paHandler(QObject* parent)
|
|
{
|
|
Q_UNUSED(parent)
|
|
}
|
|
|
|
paHandler::~paHandler()
|
|
{
|
|
|
|
if (converterThread != Q_NULLPTR) {
|
|
converterThread->quit();
|
|
converterThread->wait();
|
|
}
|
|
|
|
if (isInitialized) {
|
|
Pa_StopStream(audio);
|
|
Pa_CloseStream(audio);
|
|
}
|
|
|
|
}
|
|
|
|
bool paHandler::init(audioSetup setup)
|
|
{
|
|
if (isInitialized) {
|
|
return false;
|
|
}
|
|
|
|
this->setup = setup;
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "PortAudio handler starting:" << setup.name;
|
|
|
|
if (setup.portInt == -1)
|
|
{
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found.";
|
|
return false;
|
|
}
|
|
|
|
radioFormat = toQAudioFormat(setup.codec, setup.sampleRate);
|
|
|
|
qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name <<
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
", bits" << radioFormat.sampleSize() <<
|
|
#else
|
|
", format" << radioFormat.sampleFormat() <<
|
|
#endif
|
|
", codec" << setup.codec <<
|
|
", latency" << setup.latency <<
|
|
", localAFGain" << setup.localAFgain <<
|
|
", radioChan" << radioFormat.channelCount() <<
|
|
", resampleQuality" << setup.resampleQuality <<
|
|
", samplerate" << radioFormat.sampleRate() <<
|
|
", uLaw" << setup.ulaw;
|
|
|
|
PaError err;
|
|
#ifdef Q_OS_WIN
|
|
CoInitialize(0);
|
|
#endif
|
|
|
|
//err = Pa_Initialize();
|
|
//if (err != paNoError)
|
|
//{
|
|
// qDebug(logAudio()) << "Portaudio initialized";
|
|
//}
|
|
|
|
|
|
codecType codec = LPCM;
|
|
if (setup.codec == 0x01 || setup.codec == 0x20)
|
|
codec = PCMU;
|
|
else if (setup.codec == 0x40 || setup.codec == 0x40)
|
|
codec = OPUS;
|
|
|
|
memset(&aParams, 0, sizeof(PaStreamParameters));
|
|
|
|
aParams.device = setup.portInt;
|
|
info = Pa_GetDeviceInfo(aParams.device);
|
|
|
|
qDebug(logAudio()) << "PortAudio" << (setup.isinput ? "Input" : "Output") << setup.portInt << "Input Channels" << info->maxInputChannels << "Output Channels" << info->maxOutputChannels;
|
|
|
|
|
|
if (setup.isinput) {
|
|
nativeFormat.setChannelCount(info->maxInputChannels);
|
|
}
|
|
else {
|
|
nativeFormat.setChannelCount(info->maxOutputChannels);
|
|
}
|
|
|
|
aParams.suggestedLatency = (float)setup.latency / 1000.0f;
|
|
nativeFormat.setSampleRate(info->defaultSampleRate);
|
|
aParams.sampleFormat = paFloat32;
|
|
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
nativeFormat.setSampleSize(32);
|
|
nativeFormat.setSampleType(QAudioFormat::Float);
|
|
nativeFormat.setByteOrder(QAudioFormat::LittleEndian);
|
|
nativeFormat.setCodec("audio/pcm");
|
|
#else
|
|
nativeFormat.setSampleFormat(QAudioFormat::Float);
|
|
#endif
|
|
|
|
if (nativeFormat.channelCount() > 2) {
|
|
nativeFormat.setChannelCount(2);
|
|
}
|
|
else if (nativeFormat.channelCount() < 1)
|
|
{
|
|
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup.";
|
|
return false;
|
|
}
|
|
|
|
if (nativeFormat.channelCount() == 1 && radioFormat.channelCount() == 2) {
|
|
nativeFormat.setChannelCount(2);
|
|
}
|
|
|
|
aParams.channelCount = nativeFormat.channelCount();
|
|
|
|
if (nativeFormat.sampleRate() < 44100) {
|
|
nativeFormat.setSampleRate(48000);
|
|
}
|
|
|
|
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleSize" << nativeFormat.sampleSize() << "Channel Count" << nativeFormat.channelCount() <<
|
|
"Sample Rate" << nativeFormat.sampleRate() << "Codec" << codec << "Sample Type" << nativeFormat.sampleType();
|
|
#else
|
|
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleFormat" << nativeFormat.sampleFormat() << "Channel Count" << nativeFormat.channelCount() <<
|
|
"Sample Rate" << nativeFormat.sampleRate() << "Codec" << codec;
|
|
#endif
|
|
|
|
// We "hopefully" now have a valid format that is supported so try connecting
|
|
|
|
converter = new audioConverter();
|
|
converterThread = new QThread(this);
|
|
if (setup.isinput) {
|
|
converterThread->setObjectName("audioConvIn()");
|
|
}
|
|
else {
|
|
converterThread->setObjectName("audioConvOut()");
|
|
}
|
|
converter->moveToThread(converterThread);
|
|
|
|
connect(this, SIGNAL(setupConverter(QAudioFormat, codecType, QAudioFormat, codecType, quint8, quint8)), converter, SLOT(init(QAudioFormat, codecType, QAudioFormat, codecType, quint8, quint8)));
|
|
connect(converterThread, SIGNAL(finished()), converter, SLOT(deleteLater()));
|
|
connect(this, SIGNAL(sendToConverter(audioPacket)), converter, SLOT(convert(audioPacket)));
|
|
converterThread->start(QThread::TimeCriticalPriority);
|
|
|
|
aParams.hostApiSpecificStreamInfo = NULL;
|
|
|
|
// Per channel chunk size.
|
|
this->chunkSize = nativeFormat.framesForDuration(setup.blockSize * 1000);
|
|
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Chunk size" << this->chunkSize;
|
|
// Check the format is supported
|
|
|
|
|
|
|
|
err = -1;
|
|
int errCount = 0;
|
|
while (err != paNoError) {
|
|
if (setup.isinput) {
|
|
err = Pa_IsFormatSupported(&aParams, NULL, nativeFormat.sampleRate());
|
|
}
|
|
else
|
|
{
|
|
err = Pa_IsFormatSupported(NULL, &aParams, nativeFormat.sampleRate());
|
|
}
|
|
if (err == paInvalidChannelCount)
|
|
{
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported channel count" << aParams.channelCount;
|
|
if (aParams.channelCount == 2) {
|
|
aParams.channelCount = 1;
|
|
nativeFormat.setChannelCount(1);
|
|
}
|
|
else {
|
|
aParams.channelCount = 2;
|
|
nativeFormat.setChannelCount(2);
|
|
}
|
|
}
|
|
if (err == paInvalidSampleRate)
|
|
{
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample rate" << nativeFormat.sampleRate();
|
|
nativeFormat.setSampleRate(44100);
|
|
}
|
|
if (err == paSampleFormatNotSupported)
|
|
{
|
|
aParams.sampleFormat = paInt16;
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample Format" << nativeFormat.sampleType();
|
|
nativeFormat.setSampleType(QAudioFormat::SignedInt);
|
|
nativeFormat.setSampleSize(16);
|
|
#else
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample Format" << nativeFormat.sampleFormat();
|
|
nativeFormat.setSampleFormat(QAudioFormat::Int16);
|
|
#endif
|
|
}
|
|
|
|
errCount++;
|
|
if (errCount > 5) {
|
|
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "Cannot find suitable format, aborting:" << Pa_GetErrorText(err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (setup.isinput) {
|
|
|
|
err = Pa_OpenStream(&audio, &aParams, 0, nativeFormat.sampleRate(), this->chunkSize, paNoFlag, &paHandler::staticWrite, (void*)this);
|
|
emit setupConverter(nativeFormat, codecType::LPCM, radioFormat, codec, 7, setup.resampleQuality);
|
|
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedInput(audioPacket)));
|
|
}
|
|
else {
|
|
err = Pa_OpenStream(&audio, 0, &aParams, nativeFormat.sampleRate(), this->chunkSize, paNoFlag, NULL, NULL);
|
|
emit setupConverter(radioFormat, codec, nativeFormat, codecType::LPCM, 7, setup.resampleQuality);
|
|
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedOutput(audioPacket)));
|
|
}
|
|
|
|
if (err == paNoError) {
|
|
err = Pa_StartStream(audio);
|
|
}
|
|
if (err == paNoError) {
|
|
isInitialized = true;
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "device successfully opened";
|
|
}
|
|
else {
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "failed to open device" << Pa_GetErrorText(err);
|
|
}
|
|
|
|
this->setVolume(setup.localAFgain);
|
|
|
|
return isInitialized;
|
|
}
|
|
|
|
|
|
void paHandler::setVolume(unsigned char volume)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
this->volume = audiopot[volume] * 5;
|
|
#else
|
|
this->volume = audiopot[volume];
|
|
#endif
|
|
}
|
|
|
|
void paHandler::incomingAudio(audioPacket packet)
|
|
{
|
|
packet.volume = volume;
|
|
if (Pa_IsStreamActive(audio) == 1) {
|
|
emit sendToConverter(packet);
|
|
}
|
|
else
|
|
{
|
|
Pa_StartStream(audio);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
int paHandler::writeData(const void* inputBuffer, void* outputBuffer,
|
|
unsigned long nFrames, const PaStreamCallbackTimeInfo * streamTime,
|
|
PaStreamCallbackFlags status)
|
|
{
|
|
Q_UNUSED(outputBuffer);
|
|
Q_UNUSED(streamTime);
|
|
Q_UNUSED(status);
|
|
audioPacket packet;
|
|
packet.time = QTime::currentTime();
|
|
packet.sent = 0;
|
|
packet.volume = volume;
|
|
memcpy(&packet.guid, setup.guid, GUIDLEN);
|
|
packet.data.append((char*)inputBuffer, nFrames*nativeFormat.bytesPerFrame());
|
|
emit sendToConverter(packet);
|
|
|
|
if (status == paInputUnderflow) {
|
|
isUnderrun = true;
|
|
}
|
|
else if (status == paInputOverflow) {
|
|
isOverrun = true;
|
|
}
|
|
else
|
|
{
|
|
isUnderrun = false;
|
|
isOverrun = false;
|
|
}
|
|
|
|
return paContinue;
|
|
}
|
|
|
|
|
|
void paHandler::convertedOutput(audioPacket packet) {
|
|
|
|
if (packet.data.size() > 0) {
|
|
|
|
if (Pa_IsStreamActive(audio) == 1) {
|
|
PaError err = Pa_WriteStream(audio, (char*)packet.data.data(), packet.data.size() / nativeFormat.bytesPerFrame());
|
|
|
|
if (err != paNoError) {
|
|
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Error writing audio!";
|
|
}
|
|
const PaStreamInfo* info = Pa_GetStreamInfo(audio);
|
|
currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->outputLatency * 1000);
|
|
}
|
|
|
|
amplitude = packet.amplitudePeak;
|
|
emit haveLevels(getAmplitude(), static_cast<quint16>(packet.amplitudeRMS * 255.0), setup.latency, currentLatency, isUnderrun, isOverrun);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void paHandler::convertedInput(audioPacket packet)
|
|
{
|
|
if (packet.data.size() > 0) {
|
|
emit haveAudioData(packet);
|
|
amplitude = packet.amplitudePeak;
|
|
const PaStreamInfo* info = Pa_GetStreamInfo(audio);
|
|
currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->inputLatency * 1000);
|
|
emit haveLevels(getAmplitude(), packet.amplitudeRMS, setup.latency, currentLatency, isUnderrun, isOverrun);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void paHandler::changeLatency(const quint16 newSize)
|
|
{
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency;
|
|
setup.latency = newSize;
|
|
latencyAllowance = 0;
|
|
}
|
|
|
|
int paHandler::getLatency()
|
|
{
|
|
return currentLatency;
|
|
}
|
|
|
|
|
|
quint16 paHandler::getAmplitude()
|
|
{
|
|
return static_cast<quint16>(amplitude * 255.0);
|
|
}
|