wfview/pahandler.cpp

482 wiersze
16 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 (converterToMonitorThread != Q_NULLPTR)
{
converterToMonitorThread->quit();
converterToMonitorThread->wait();
}
if (converterThread != Q_NULLPTR) {
converterThread->quit();
converterThread->wait();
}
if (isInitialized) {
Pa_StopStream(audio);
Pa_CloseStream(audio);
}
}
void paHandler::setSharedAudioData(sharedAudioType *m)
{
// no allocation is handled here, we merely point to the new
// item.
this->sharedAudioInfo = m;
this->monitorFormat = m->monitorFormat;
}
void paHandler::acceptMonitorAudioFormat(QAudioFormat mformat)
{
qDebug(logAudio()) << "Monitor audio format received into tx audio handler.";
this->monitorFormat = mformat;
this->sharedAudioInfo->monitorFormat = mformat;
emit setupMonitorStreamConverter(outFormat, monitorFormat, 7, setup.resampleQuality);
connect(converterToMonitor, SIGNAL(converted(audioPacket)), this, SLOT(convertedMonitorAudio(audioPacket)));
if(checkFormatsSame(&outFormat, &monitorFormat))
{
convertMonitorFormat = false;
} else {
convertMonitorFormat = true;
qDebug(logAudio()) << "outFormat: " << outFormat;
qDebug(logAudio()) << "monitorFormat: " << monitorFormat;
}
qInfo(logAudio()) << "Conversion for monitor audio: " << convertMonitorFormat;
haveMonitorInformation = true;
}
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(sharedAudioInfo, setup.isinput);
converterThread = new QThread(this);
if (setup.isinput) {
converterThread->setObjectName("audioConvIn()");
converterToMonitor = new audioConverter(sharedAudioInfo, setup.isinput);
converterToMonitorThread = new QThread(this);
converterToMonitorThread->setObjectName("audioConvMon");
converterToMonitor->moveToThread(converterToMonitorThread);
connect(converterToMonitorThread, SIGNAL(finished()), converterToMonitor, SLOT(deleteLater()));
connect(this, SIGNAL(setupMonitorStreamConverter(QAudioFormat,QAudioFormat,quint8,quint8)), converterToMonitor, SLOT(init(QAudioFormat,QAudioFormat,quint8,quint8)));
connect(this, SIGNAL(sendToMonitorConverter(audioPacket)), converterToMonitor, SLOT(convert(audioPacket)));
converterToMonitorThread->start(QThread::TimeCriticalPriority);
}
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);
//emit setupMonitorStreamConverter(outFormat, monitorFormat, 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);
emit haveAudioFormat(outFormat);
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
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "setVolume: " << volume << "(" << this->volume << ")";
}
void paHandler::incomingAudio(audioPacket packet)
{
packet.volume = volume;
emit sendToConverter(packet);
if(setup.isinput)
{
// Try sending directly to the hardware:
// (not currently working)
//convertedOutput(packet);
if(haveMonitorInformation)
{
if(convertMonitorFormat)
{
// Convert format: (if needed)
emit sendToMonitorConverter(packet);
} else {
// if the formats are actually the same, no need to convert:
convertedMonitorAudio(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(setup.isinput)
{
emit sendToMonitorConverter(packet);
}
if (status == paInputUnderflow) {
isUnderrun = true;
}
else if (status == paInputOverflow) {
isOverrun = true;
}
else
{
isUnderrun = false;
isOverrun = false;
}
return paContinue;
}
void paHandler::mixAudio(QByteArray *dest, unsigned char *monitorSource)
{
// TODO: write versions of mix audio for the various formats that port
// audio supports.
int size = dest->size();
float* mon = reinterpret_cast<float*>(monitorSource);
float* dst = reinterpret_cast<float*>(dest->data());
// 32 bits per float sample means the size is divide by four.
int floatSize = size / 4;
for(int s=0; s < floatSize; s++)
{
dst[s] = dst[s] + sharedAudioInfo->monitorVolume*mon[s];
}
}
void paHandler::convertedOutput(audioPacket packet) {
if (packet.data.size() > 0) {
if (Pa_IsStreamActive(audio) == 1) {
if(sharedAudioInfo->monitorFeatureOn &&
sharedAudioInfo->monitorAudioAvailable &&
(sharedAudioInfo->sentPacketCount > audioMonitorBufferSize/2))
{
//qDebug(logAudio()) << "rx audio packet size: " << packet.data.size() << ", monitor audio size: " << sharedAudioInfo->monitorAudioBuffer[0].data.size();
//int readPos = sharedAudioInfo->bufferReadPosition;
int readPos = (monitorBufferPosition++)%audioMonitorBufferSize;
/*
if( (readPos+2)%audioMonitorBufferSize == sharedAudioInfo->bufferWritePosition)
{
// then we are too close, let's step back
qDebug(logAudio()) << "Warning, monitor buffer read is too close, moving back. Write position: " << sharedAudioInfo->bufferWritePosition << ", read position: " << readPos;
monitorBufferPosition = (sharedAudioInfo->bufferWritePosition-3)%audioMonitorBufferSize;
readPos = monitorBufferPosition;
}
*/
// if(packet.data.size() != sharedAudioInfo->monitorAudioBuffer[readPos].data.size())
// {
// qDebug(logAudio()) << "ERROR, size mismatch. packet is " << packet.data.size() << "bytes, monitor audio is " << sharedAudioInfo->monitorAudioBuffer[readPos].data.size() << "bytes.";
// }
mixAudio(&packet.data, sharedAudioInfo->monitorAudioBufferChar[readPos]);
// Copy:
/*
for(int p=0; p < packet.data.size(); p++)
{
// monitor only test: (this sounds bad currently)
//packet.data[p] = sharedAudioInfo->monitorAudioBuffer[readPos].data.at(p);
packet.data[p] = sharedAudioInfo->monitorAudioBufferChar[readPos][p];
// Set to self:
//packet.data[p] = packet.data[p];
// Mix:
//packet.data[p] = packet.data[p] + (unsigned char)(sharedAudioInfo->monitorVolume*sharedAudioInfo->monitorAudioBuffer[sharedAudioInfo->bufferReadPosition].data.at(p));
}
*/
}
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::convertedMonitorAudio(audioPacket audio)
{
// TODO: copy the audio packet to the shared monitor data
// if this function was called, we can assume we are isInput.
if(sharedAudioInfo->monitorFeatureOn)
{
int position = sharedAudioInfo->bufferWritePosition;
int size = audio.data.size();
// TODO: We are really just interested in the QByteArray data,
// and we must be mindful that this copy is by-reference.
//
//qDebug(logAudio()) << "monitor capture audio packet size: " << audio.data.size();
// dumb-yet-deep Copy:
//sharedAudioInfo->monitorAudioBuffer[position].data.clear();
for(int p=0; p < size; p++)
{
sharedAudioInfo->monitorAudioBufferChar[position][p] = audio.data.at(p);
//sharedAudioInfo->monitorAudioBuffer[position].data.append(audio.data.at(p));
}
//sharedAudioInfo->monitorAudioBuffer[position] = audio;
sharedAudioInfo->bufferReadPosition = position;
sharedAudioInfo->bufferWritePosition = (position+1)%audioMonitorBufferSize;
sharedAudioInfo->monitorAudioAvailable = true;
sharedAudioInfo->sentPacketCount++;
}
}
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);
}
}
bool paHandler::checkFormatsSame(QAudioFormat *a, QAudioFormat *b)
{
bool same = true;
same = same && (a->codec() == b->codec());
same = same && (a->sampleRate() == b->sampleRate());
same = same && (a->byteOrder() == b->byteOrder());
same = same && (a->channelCount() == b->channelCount());
same = same && (a->sampleSize() == b->sampleSize());
same = same && (a->sampleType() == b->sampleType());
return same;
}
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);
}