kopia lustrzana https://gitlab.com/eliggett/wfview
313 wiersze
8.4 KiB
C++
313 wiersze
8.4 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;
|
|
}
|
|
|
|
inFormat = toQAudioFormat(setup.codec, setup.sampleRate);
|
|
|
|
qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name <<
|
|
", bits" << inFormat.sampleSize() <<
|
|
", codec" << setup.codec <<
|
|
", latency" << setup.latency <<
|
|
", localAFGain" << setup.localAFgain <<
|
|
", radioChan" << inFormat.channelCount() <<
|
|
", resampleQuality" << setup.resampleQuality <<
|
|
", samplerate" << inFormat.sampleRate() <<
|
|
", uLaw" << setup.ulaw;
|
|
|
|
PaError err;
|
|
#ifdef Q_OS_WIN
|
|
CoInitialize(0);
|
|
#endif
|
|
|
|
//err = Pa_Initialize();
|
|
//if (err != paNoError)
|
|
//{
|
|
// qDebug(logAudio()) << "Portaudio initialized";
|
|
//}
|
|
|
|
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) {
|
|
outFormat.setChannelCount(info->maxInputChannels);
|
|
}
|
|
else {
|
|
outFormat.setChannelCount(info->maxOutputChannels);
|
|
}
|
|
|
|
aParams.suggestedLatency = (float)setup.latency / 1000.0f;
|
|
outFormat.setSampleRate(info->defaultSampleRate);
|
|
aParams.sampleFormat = paFloat32;
|
|
outFormat.setSampleSize(32);
|
|
outFormat.setSampleType(QAudioFormat::Float);
|
|
outFormat.setByteOrder(QAudioFormat::LittleEndian);
|
|
outFormat.setCodec("audio/pcm");
|
|
|
|
|
|
if (outFormat.channelCount() > 2) {
|
|
outFormat.setChannelCount(2);
|
|
}
|
|
else if (outFormat.channelCount() < 1)
|
|
{
|
|
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup.";
|
|
return false;
|
|
}
|
|
|
|
if (inFormat.channelCount() < outFormat.channelCount()) {
|
|
outFormat.setChannelCount(inFormat.channelCount());
|
|
}
|
|
|
|
aParams.channelCount = outFormat.channelCount();
|
|
|
|
if (outFormat.sampleRate() < 44100) {
|
|
outFormat.setSampleRate(48000);
|
|
}
|
|
|
|
|
|
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleSize" << outFormat.sampleSize() << "Channel Count" << outFormat.channelCount() <<
|
|
"Sample Rate" << outFormat.sampleRate() << "Codec" << outFormat.codec() << "Sample Type" << outFormat.sampleType();
|
|
|
|
// 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, QAudioFormat, quint8, quint8)), converter, SLOT(init(QAudioFormat, QAudioFormat, 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 = (outFormat.bytesForDuration(setup.blockSize * 1000) / sizeof(float)) * outFormat.channelCount();
|
|
|
|
// Check the format is supported
|
|
|
|
|
|
if (setup.isinput) {
|
|
err = Pa_IsFormatSupported(&aParams, NULL, outFormat.sampleRate());
|
|
}
|
|
else
|
|
{
|
|
err = Pa_IsFormatSupported(NULL,&aParams, outFormat.sampleRate());
|
|
}
|
|
|
|
if (err != paNoError) {
|
|
if (err == paInvalidChannelCount)
|
|
{
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported channel count" << aParams.channelCount;
|
|
if (aParams.channelCount == 2) {
|
|
aParams.channelCount = 1;
|
|
outFormat.setChannelCount(1);
|
|
}
|
|
else {
|
|
aParams.channelCount = 2;
|
|
outFormat.setChannelCount(2);
|
|
}
|
|
}
|
|
else if (err == paInvalidSampleRate)
|
|
{
|
|
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample rate" << outFormat.sampleRate();
|
|
outFormat.setSampleRate(44100);
|
|
}
|
|
else if (err == paSampleFormatNotSupported)
|
|
{
|
|
aParams.sampleFormat = paInt16;
|
|
outFormat.setSampleType(QAudioFormat::SignedInt);
|
|
outFormat.setSampleSize(16);
|
|
}
|
|
|
|
if (setup.isinput) {
|
|
err = Pa_IsFormatSupported(&aParams, NULL, outFormat.sampleRate());
|
|
}
|
|
else
|
|
{
|
|
err = Pa_IsFormatSupported(NULL, &aParams, outFormat.sampleRate());
|
|
}
|
|
if (err != paNoError) {
|
|
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, outFormat.sampleRate(), this->chunkSize, paNoFlag, &paHandler::staticWrite, (void*)this);
|
|
emit setupConverter(outFormat, inFormat, 7, setup.resampleQuality);
|
|
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedInput(audioPacket)));
|
|
}
|
|
else {
|
|
err = Pa_OpenStream(&audio, 0, &aParams, outFormat.sampleRate(), this->chunkSize, paNoFlag, NULL, NULL);
|
|
emit setupConverter(inFormat, outFormat, 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;
|
|
emit sendToConverter(packet);
|
|
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*inFormat.channelCount()*sizeof(float));
|
|
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() / sizeof(float) / outFormat.channelCount());
|
|
|
|
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;
|
|
}
|
|
|
|
int paHandler::getLatency()
|
|
{
|
|
return currentLatency;
|
|
}
|
|
|
|
|
|
quint16 paHandler::getAmplitude()
|
|
{
|
|
return static_cast<quint16>(amplitude * 255.0);
|
|
}
|