wfview/audiohandler.cpp

1155 wiersze
36 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"
#include "ulaw.h"
audioHandler::audioHandler(QObject* parent)
{
Q_UNUSED(parent)
}
audioHandler::~audioHandler()
{
if (isInitialized) {
#if defined(RTAUDIO)
try {
audio->abortStream();
audio->closeStream();
}
catch (RtAudioError& e) {
qInfo(logAudio()) << "Error closing stream:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage());
}
delete audio;
#elif defined(PORTAUDIO)
#else
stop();
#endif
}
if (ringBuf != Q_NULLPTR) {
delete ringBuf;
}
if (resampler != Q_NULLPTR) {
speex_resampler_destroy(resampler);
qDebug(logAudio()) << "Resampler closed";
}
}
bool audioHandler::init(audioSetup setupIn)
{
if (isInitialized) {
return false;
}
/*
0x01 uLaw 1ch 8bit
0x02 PCM 1ch 8bit
0x04 PCM 1ch 16bit
0x08 PCM 2ch 8bit
0x10 PCM 2ch 16bit
0x20 uLaw 2ch 8bit
*/
setup = setupIn;
setup.radioChan = 1;
setup.bits = 8;
if (setup.codec == 0x01 || setup.codec == 0x20) {
setup.ulaw = true;
}
if (setup.codec == 0x08 || setup.codec == 0x10 || setup.codec == 0x20) {
setup.radioChan = 2;
}
if (setup.codec == 0x04 || setup.codec == 0x10) {
setup.bits = 16;
}
ringBuf = new wilt::Ring<audioPacket>(100); // Should be customizable.
tempBuf.sent = 0;
if(!setup.isinput)
{
this->setVolume(setup.localAFgain);
}
#if defined(RTAUDIO)
#if !defined(Q_OS_MACX)
options.flags = ((!RTAUDIO_HOG_DEVICE) | (RTAUDIO_MINIMIZE_LATENCY));
#endif
#if defined(Q_OS_LINUX)
audio = new RtAudio(RtAudio::Api::LINUX_ALSA);
#elif defined(Q_OS_WIN)
audio = new RtAudio(RtAudio::Api::WINDOWS_WASAPI);
#elif defined(Q_OS_MACX)
audio = new RtAudio(RtAudio::Api::MACOSX_CORE);
#endif
if (setup.port > 0) {
aParams.deviceId = setup.port;
}
else if (setup.isinput) {
aParams.deviceId = audio->getDefaultInputDevice();
}
else {
aParams.deviceId = audio->getDefaultOutputDevice();
}
aParams.firstChannel = 0;
try {
info = audio->getDeviceInfo(aParams.deviceId);
}
catch (RtAudioError& e) {
qInfo(logAudio()) << "Device error:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage());
return isInitialized;
}
if (info.probed)
{
// if "preferred" sample rate is 44100, try 48K instead
if (info.preferredSampleRate == (unsigned int)44100) {
qDebug(logAudio()) << "Preferred sample rate 44100, trying 48000";
this->nativeSampleRate = 48000;
}
else {
this->nativeSampleRate = info.preferredSampleRate;
}
// Per channel chunk size.
this->chunkSize = (this->nativeSampleRate / 50);
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") successfully probed";
if (info.nativeFormats == 0)
{
qInfo(logAudio()) << " No natively supported data formats!";
return false;
}
else {
qDebug(logAudio()) << " Supported formats:" <<
(info.nativeFormats & RTAUDIO_SINT8 ? "8-bit int," : "") <<
(info.nativeFormats & RTAUDIO_SINT16 ? "16-bit int," : "") <<
(info.nativeFormats & RTAUDIO_SINT24 ? "24-bit int," : "") <<
(info.nativeFormats & RTAUDIO_SINT32 ? "32-bit int," : "") <<
(info.nativeFormats & RTAUDIO_FLOAT32 ? "32-bit float," : "") <<
(info.nativeFormats & RTAUDIO_FLOAT64 ? "64-bit float," : "");
qInfo(logAudio()) << " Preferred sample rate:" << info.preferredSampleRate;
if (setup.isinput) {
devChannels = info.inputChannels;
}
else {
devChannels = info.outputChannels;
}
qInfo(logAudio()) << " Channels:" << devChannels;
if (devChannels > 2) {
devChannels = 2;
}
aParams.nChannels = devChannels;
}
qInfo(logAudio()) << " chunkSize: " << chunkSize;
try {
if (setup.isinput) {
audio->openStream(NULL, &aParams, RTAUDIO_SINT16, this->nativeSampleRate, &this->chunkSize, &staticWrite, this, &options);
}
else {
audio->openStream(&aParams, NULL, RTAUDIO_SINT16, this->nativeSampleRate, &this->chunkSize, &staticRead, this, &options);
}
audio->startStream();
isInitialized = true;
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "device successfully opened";
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "detected latency:" << audio->getStreamLatency();
}
catch (RtAudioError& e) {
qInfo(logAudio()) << "Error opening:" << QString::fromStdString(e.getMessage());
}
}
else
{
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") could not be probed, check audio configuration!";
}
#elif defined(PORTAUDIO)
#else
format.setSampleSize(16);
format.setChannelCount(2);
format.setSampleRate(INTERNAL_SAMPLE_RATE);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
if (setup.port.isNull())
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found. You probably need to install libqt5multimedia-plugins.";
return false;
}
else if (!setup.port.isFormatSupported(format))
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Format not supported, choosing nearest supported format - which may not work!";
format=setup.port.nearestFormat(format);
}
if (format.channelCount() > 2) {
format.setChannelCount(2);
}
else if (format.channelCount() < 1)
{
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup.";
return false;
}
devChannels = format.channelCount();
nativeSampleRate = format.sampleRate();
// chunk size is always relative to Internal Sample Rate.
this->chunkSize = (nativeSampleRate / 50);
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Internal: sample rate" << format.sampleRate() << "channel count" << format.channelCount();
// We "hopefully" now have a valid format that is supported so try connecting
if (setup.isinput) {
audioInput = new QAudioInput(setup.port, format, this);
connect(audioInput, SIGNAL(notify()), SLOT(notified()));
connect(audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
isInitialized = true;
}
else {
audioOutput = new QAudioOutput(setup.port, format, this);
#ifdef Q_OS_MAC
audioOutput->setBufferSize(chunkSize*4);
#endif
connect(audioOutput, SIGNAL(notify()), SLOT(notified()));
connect(audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
isInitialized = true;
}
#endif
// Setup resampler if it is needed.
int resample_error = 0;
if (setup.isinput) {
resampler = wf_resampler_init(devChannels, nativeSampleRate, setup.samplerate, setup.resampleQuality, &resample_error);
}
else {
resampler = wf_resampler_init(devChannels, setup.samplerate, this->nativeSampleRate, setup.resampleQuality, &resample_error);
}
wf_resampler_get_ratio(resampler, &ratioNum, &ratioDen);
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "wf_resampler_init() returned: " << resample_error << " ratioNum" << ratioNum << " ratioDen" << ratioDen;
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "thread id" << QThread::currentThreadId();
// Plugin init
if(!setup.isinput)
{
SAMPLE_RATE = format.sampleRate();
setupLADSP();
if (psDescriptor->activate != NULL)
psDescriptor->activate(handle);
}
#if !defined (RTAUDIO) && !defined(PORTAUDIO)
if (isInitialized) {
this->start();
}
#endif
return isInitialized;
}
#if !defined (RTAUDIO) && !defined(PORTAUDIO)
void audioHandler::start()
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "start() running";
if ((audioOutput == Q_NULLPTR || audioOutput->state() != QAudio::StoppedState) &&
(audioInput == Q_NULLPTR || audioInput->state() != QAudio::StoppedState)) {
return;
}
if (setup.isinput) {
#ifdef Q_OS_MACX
this->open(QIODevice::WriteOnly);
#else
this->open(QIODevice::WriteOnly | QIODevice::Unbuffered);
#endif
audioInput->start(this);
}
else {
#ifdef Q_OS_MACX
this->open(QIODevice::ReadOnly);
#else
this->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
#endif
audioOutput->start(this);
}
}
#endif
void audioHandler::setupLADSP()
{
// setup for the plugin:
int lPluginIndex;
pre_control = 0.99f;
void *pvPluginHandle = loadLADSPAPluginLibrary(pcPluginFilename);
dlerror();
pfDescriptorFunction
= (LADSPA_Descriptor_Function)dlsym(pvPluginHandle, "ladspa_descriptor");
if (!pfDescriptorFunction) {
const char * pcError = dlerror();
if (pcError)
qInfo(logAudio()) << __PRETTY_FUNCTION__ << QString("Unable to find ladspa_descriptor() function in plugin library file %1: %2. Are you sure this is a LADSPA plugin file?").arg(pcPluginFilename).arg(pcError);
return;
}
BUF_SIZE = 2728; // starting with the largest likely size. I think we can do half this actually, due to 16 bit needing half as much space as 8 bit.
qDebug(logAudio()) << __PRETTY_FUNCTION__ << ": Setting BUF_SIZE to " << BUF_SIZE;
qDebug(logAudio()) << __PRETTY_FUNCTION__ << ": Setting sample rate to: " << SAMPLE_RATE;
pInBuffer[0]= (float*)malloc(sizeof(LADSPA_Data) * BUF_SIZE/2);
pInBuffer[1]= (float*)malloc(sizeof(LADSPA_Data) * BUF_SIZE/2);
pOutBuffer[0]= (float*)malloc(sizeof(LADSPA_Data) * BUF_SIZE/2);
pOutBuffer[1]= (float*)malloc(sizeof(LADSPA_Data) * BUF_SIZE/2);
for (lPluginIndex = 0;; lPluginIndex++) {
psDescriptor = pfDescriptorFunction(lPluginIndex);
if (!psDescriptor)
break;
if (pcPluginLabel != NULL) {
if (strcmp(pcPluginLabel, psDescriptor->Label) != 0)
continue;
}
// got mono_amp
handle = psDescriptor->instantiate(psDescriptor, SAMPLE_RATE);
if (handle == NULL) {
qInfo(logAudio()) << QString("Can't instantiate plugin %1").arg(pcPluginLabel);
return;
}
// get ports
int lPortIndex;
int lControlPortIndex = 0;
qDebug(logAudio()) << QString("Num ports %1").arg(psDescriptor->PortCount);
for (lPortIndex = 0;
lPortIndex < psDescriptor->PortCount;
lPortIndex++) {
if (LADSPA_IS_PORT_AUDIO
(psDescriptor->PortDescriptors[lPortIndex])) {
if (LADSPA_IS_PORT_INPUT
(psDescriptor->PortDescriptors[lPortIndex])) {
qDebug(logAudio()) << QString("input %1").arg(lPortIndex);
lInputPortIndex = lPortIndex;
psDescriptor->connect_port(handle,
lInputPortIndex, pInBuffer[inBufferIndex++]);
} else if (LADSPA_IS_PORT_OUTPUT
(psDescriptor->PortDescriptors[lPortIndex])) {
qDebug(logAudio()) << QString("output %1").arg(lPortIndex);
lOutputPortIndex = lPortIndex;
psDescriptor->connect_port(handle,
lOutputPortIndex, pOutBuffer[outBufferIndex++]);
}
}
if (LADSPA_IS_PORT_CONTROL
(psDescriptor->PortDescriptors[lPortIndex])) {
qDebug(logAudio()) << QString("control %1").arg(lPortIndex);
psDescriptor->connect_port(handle,
lPortIndex, &controls[lControlPortIndex++]);
}
}
// we've got what we wanted, get out of this loop
break;
}
if ((psDescriptor == NULL) ||
(lInputPortIndex == -1) ||
(lOutputPortIndex == -1)) {
qInfo(logAudio()) << "Can't find plugin information";
return;
}
if (psDescriptor->activate != NULL)
psDescriptor->activate(handle);
pluginOutputEnable = true;
}
void audioHandler::enablePluginOutput(bool enable)
{
this->pluginOutputEnable = enable;
}
void audioHandler::runPlugin()
{
// numread actually sets the
int numSamplesToProcess= BUF_SIZE/2;
// if (psDescriptor->activate != NULL)
// psDescriptor->activate(handle);
// grab audio (already done, no sound file to read from)
// sfload does this: copy from input file to pInBuffer, at size BUF_SIZE
psDescriptor->run(handle, numSamplesToProcess); // run the audio through the plugin. Number of pieces processed is numread. Compare to size of data needing to be read and run again if needed.
//qDebug(logAudio()) << "Number of samples processed: " << numSamplesToProcess;
}
void audioHandler::setVolume(unsigned char volume)
{
//this->volume = (qreal)volume/255.0;
this->volume = audiopot[volume];
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "setVolume: " << volume << "(" << this->volume << ")";
}
/// <summary>
/// This function processes the incoming audio FROM the radio and pushes it into the playback buffer *data
/// </summary>
/// <param name="data"></param>
/// <param name="maxlen"></param>
/// <returns></returns>
#if defined(RTAUDIO)
int audioHandler::readData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status)
{
Q_UNUSED(inputBuffer);
Q_UNUSED(streamTime);
if (status == RTAUDIO_OUTPUT_UNDERFLOW)
qDebug(logAudio()) << "Underflow detected";
int nBytes = nFrames * devChannels * 2; // This is ALWAYS 2 bytes per sample and 2 channels
quint8* buffer = (quint8*)outputBuffer;
#elif defined(PORTAUDIO)
#else
qint64 audioHandler::readData(char* buffer, qint64 nBytes)
{
#endif
// Calculate output length, always full samples
int sentlen = 0;
if (!isReady) {
isReady = true;
}
if (ringBuf->size()>0)
{
// Output buffer is ALWAYS 16 bit.
//qDebug(logAudio()) << "Read: nFrames" << nFrames << "nBytes" << nBytes;
while (sentlen < nBytes)
{
audioPacket packet;
if (!ringBuf->try_read(packet))
{
qDebug() << "No more data available but buffer is not full! sentlen:" << sentlen << " nBytes:" << nBytes ;
break;
}
currentLatency = packet.time.msecsTo(QTime::currentTime());
// This shouldn't be required but if we did output a partial packet
// This will add the remaining packet data to the output buffer.
if (tempBuf.sent != tempBuf.data.length())
{
int send = qMin((int)nBytes - sentlen, tempBuf.data.length() - tempBuf.sent);
memcpy(buffer + sentlen, tempBuf.data.constData() + tempBuf.sent, send);
tempBuf.sent = tempBuf.sent + send;
sentlen = sentlen + send;
if (tempBuf.sent != tempBuf.data.length())
{
// We still don't have enough buffer space for this?
break;
}
//qDebug(logAudio()) << "Adding partial:" << send;
}
while (currentLatency > setup.latency) {
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Packet " << hex << packet.seq <<
" arrived too late (increase output latency!) " <<
dec << packet.time.msecsTo(QTime::currentTime()) << "ms";
lastSeq = packet.seq;
if (!ringBuf->try_read(packet))
break;
currentLatency = packet.time.msecsTo(QTime::currentTime());
}
int send = qMin((int)nBytes - sentlen, packet.data.length());
memcpy(buffer + sentlen, packet.data.constData(), send);
sentlen = sentlen + send;
if (send < packet.data.length())
{
//qDebug(logAudio()) << "Asking for partial, sent:" << send << "packet length" << packet.data.length();
tempBuf = packet;
tempBuf.sent = tempBuf.sent + send;
lastSeq = packet.seq;
break;
}
if (packet.seq <= lastSeq) {
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Duplicate/early audio packet: " << hex << lastSeq << " got " << hex << packet.seq;
}
else if (packet.seq != lastSeq + 1) {
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Missing audio packet(s) from: " << hex << lastSeq + 1 << " to " << hex << packet.seq - 1;
}
lastSeq = packet.seq;
}
}
//qDebug(logAudio()) << "looking for: " << nBytes << " got: " << sentlen;
// fill the rest of the buffer with silence
if (nBytes > sentlen) {
memset(buffer+sentlen,0,nBytes-sentlen);
}
#if defined(RTAUDIO)
return 0;
#elif defined(PORTAUDIO)
#else
return nBytes;
#endif
}
#if defined(RTAUDIO)
int audioHandler::writeData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status)
{
Q_UNUSED(outputBuffer);
Q_UNUSED(streamTime);
Q_UNUSED(status);
int nBytes = nFrames * devChannels * 2; // This is ALWAYS 2 bytes per sample and 2 channels
const char* data = (const char*)inputBuffer;
#elif defined(PORTAUDIO)
#else
qint64 audioHandler::writeData(const char* data, qint64 nBytes)
{
#endif
if (!isReady) {
isReady = true;
}
int sentlen = 0;
//qDebug(logAudio()) << "nFrames" << nFrames << "nBytes" << nBytes;
int chunkBytes = chunkSize * devChannels * 2;
while (sentlen < nBytes) {
if (tempBuf.sent != chunkBytes)
{
int send = qMin((int)(nBytes - sentlen), chunkBytes - tempBuf.sent);
tempBuf.data.append(QByteArray::fromRawData(data + sentlen, send));
sentlen = sentlen + send;
tempBuf.seq = 0; // Not used in TX
tempBuf.time = QTime::currentTime();
tempBuf.sent = tempBuf.sent + send;
}
else {
ringBuf->write(tempBuf);
/*
if (!ringBuf->try_write(tempBuf))
{
qDebug(logAudio()) << "outgoing audio buffer full!";
break;
} */
tempBuf.data.clear();
tempBuf.sent = 0;
}
}
//qDebug(logAudio()) << "sentlen" << sentlen;
#if defined(RTAUDIO)
return 0;
#elif defined(PORTAUDIO)
#else
return nBytes;
#endif
}
void audioHandler::incomingAudio(audioPacket inPacket)
{
// No point buffering audio until stream is actually running.
// Regardless of the radio stream format, the buffered audio will ALWAYS be
// 16bit sample interleaved stereo 48K (or whatever the native sample rate is)
if (!isInitialized && !isReady)
{
qDebug(logAudio()) << "Packet received when stream was not ready";
return;
}
//qDebug(logAudio()) << "Got" << radioSampleBits << "bits, length" << inPacket.data.length();
// Incoming data is 8bits?
if (setup.bits == 8)
{
// Current packet is 8bit so need to create a new buffer that is 16bit
QByteArray outPacket((int)inPacket.data.length() * 2 * (devChannels / setup.radioChan), (char)0xff);
qint16* out = (qint16*)outPacket.data();
for (int f = 0; f < inPacket.data.length(); f++)
{
for (int g = setup.radioChan; g <= devChannels; g++)
{
if (isUlaw)
*out++ = ulaw_decode[(quint8)inPacket.data[f]] * this->volume;
else
*out++ = (qint16)(((quint8)inPacket.data[f] << 8) - 32640 * this->volume);
}
}
inPacket.data.clear();
inPacket.data = outPacket; // Replace incoming data with converted.
}
else
{
// This is already a 16bit stream, do we need to convert to stereo?
if (setup.radioChan == 1 && devChannels > 1) {
// Yes
QByteArray outPacket(inPacket.data.length() * 2, (char)0xff); // Preset the output buffer size.
qint16* in = (qint16*)inPacket.data.data();
qint16* out = (qint16*)outPacket.data();
for (int f = 0; f < inPacket.data.length() / 2; f++)
{
*out++ = (qint16)*in * this->volume;
*out++ = (qint16)*in++ * this->volume;
}
inPacket.data.clear();
inPacket.data = outPacket; // Replace incoming data with converted.
}
else
{
// We already have the same number of channels so just update volume.
qint16* in = (qint16*)inPacket.data.data();
for (int f = 0; f < inPacket.data.length() / 2; f++)
{
*in = *in * this->volume;
in++;
}
}
}
/* We now have an array of 16bit samples in the NATIVE samplerate of the radio
If the radio sample rate is below 48000, we need to resample.
*/
//qDebug(logAudio()) << "Now 16 bit stereo, length" << inPacket.data.length();
if (ratioDen != 1) {
// We need to resample
// We have a stereo 16bit stream.
quint32 outFrames = ((inPacket.data.length() / 2 / devChannels) * ratioDen);
quint32 inFrames = (inPacket.data.length() / 2 / devChannels);
QByteArray outPacket(outFrames * 4, (char)0xff); // Preset the output buffer size.
const qint16* in = (qint16*)inPacket.data.constData();
qint16* out = (qint16*)outPacket.data();
int err = 0;
err = wf_resampler_process_interleaved_int(resampler, in, &inFrames, out, &outFrames);
if (err) {
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Resampler error " << err << " inFrames:" << inFrames << " outFrames:" << outFrames;
}
inPacket.data.clear();
inPacket.data = outPacket; // Replace incoming data with converted.
}
//qDebug(logAudio()) << "Adding packet to buffer:" << inPacket.seq << ": " << inPacket.data.length();
// Begin plugin code:
pluginMutex.lock();
// out has size inPacket.data = BUF_SIZE
// pOut/inBuffer[1] has size BUF_SIZE/2
// inPacket.data has size BUF_SIZE
// pluginOutput16bitInterlaced has size BUF_SIZE
// Flow:
// inPacket --> out --> pInBuffer --> runPlugin() --> pOutBuffer --> pluginOutput16bitInterlaced --> inPacket
//
// Note, size of qint16 array is data.size() / 2 .
//
qint16* out = (qint16*)inPacket.data.constData();
BUF_SIZE = inPacket.data.size() / 2; // divide by two because the inPacket is 8 bits per member
// and all our usage is at 16 bits per.
// --- Input and Output buffer strategy:
// The plugin does not look at the size of the allocation. And the pointer
// needs to be to the same original location as the plugin runs
// Therefore, we allocate a large amount in the setupLADSPA() function,
// and simply adjust the BUF_SIZE so that the runPlugin() call only goes
// as far as is needed into the memory.
// I think these lines caused the pointer location at the base of the array to change, no good.
// pInBuffer[0]= (float*)realloc(pInBuffer[0], sizeof(LADSPA_Data) * BUF_SIZE / 2);
// pInBuffer[1]= (float*)realloc(pInBuffer[1], sizeof(LADSPA_Data) * BUF_SIZE / 2);
// pOutBuffer[0]= (float*)realloc(pOutBuffer[0], sizeof(LADSPA_Data) * BUF_SIZE / 2);
// pOutBuffer[1]= (float*)realloc(pOutBuffer[1], sizeof(LADSPA_Data) * BUF_SIZE / 2);
//qDebug(logAudio()) << "size of inPacket data: " << inPacket.data.size();
//qDebug(logAudio()) << "size of BUF_SIZE: " << BUF_SIZE;
// Allocated for 16 bit samples, interlaced stereo
pluginOutput16bitInterlaced = (uint16_t *)malloc(sizeof(uint16_t) * BUF_SIZE );
// Copy data from "out" to pInBuffer:
// copyDataIn();
float max = 65535;
float mid = max / 2.0f;
float scalingFactor = 1.0f / max;
// NOTE: The plugin library expects audio to be floats from -1.0 to +1.0
for (int n = 0; n < BUF_SIZE; n += 2) {
pInBuffer[0][n/2] = (out[n] - mid) * scalingFactor;
pInBuffer[1][n/2] = (out[n+1] - mid) * scalingFactor;
}
// control is a number that the plugin has a pointer to.
// In this case, control is a float from 0 to 1.
control = pre_control = 0.5f;
// How to get parameters:
// cd /usr/lib/ladspa
// export LADSPA_PATH=$PWD
// listplugins
// analyzeplugin gverb_1216.so
// Place filename and label name into the audiohandler.h file
// (When you install the SDK, you get the listplugins and analyzeplugin programs.)
// The setupLADSPA() function will assign pointers to the controls array into the plugin's instance.
// The controls array has 15 slots (per the audiohandler.h file where it is allocated on the stack).
// // Modify controls: Dyson Compressor:
controls[0] = 0.0f; // peak limit (dB) -30 to 0, default 0. For the amp plugin, this is the gain.
controls[1] = 0.01f; // release time, in seconds
controls[2] = 0.8f; // "fast compression ratio", 0 to 1
controls[3] = 0.8f; // compression ratio, 0 to 1
// Modify controls: sc1_1425.so
// controls[0] = 10.0f; // "Attack time (ms)" input, control, 2 to 400, default 101.5
// controls[1] = 10.0f; // "Release time (ms)" input, control, 2 to 800, default 401
// controls[2] = -25.0f; // "Threshold level (dB)" input, control, -30 to 0, default 0
// controls[3] = 6.0f; // "Ratio (1:n)" input, control, 1 to 10, default 1
// controls[4] = 3.25f; // "Knee radius (dB)" input, control, 1 to 10, default 3.25
// controls[5] = 12.0f; // "Makeup gain (dB)" input, control, 0 to 24, default 0
// Modify controls: fad_delay_1192.so
// controls[0] = 1.0f; // delay in seconds, 1 to 10
// controls[1] = -6.0f; // feedback (dB), -70 to 0
// Modify controls: gold reverv gverb_1216.so
// controls[0] = 75.0f; // "Roomsize (m)" input, control, 1 to 300, default 75.75
// controls[1] = 15.0f; // "Reverb time (s)" input, control, 0.1 to 30, default 7.575
// controls[2] = 0.5f; // "Damping" input, control, 0 to 1, default 0.5
// controls[3] = 0.75f; // "Input bandwidth" input, control, 0 to 1, default 0.75
// controls[4] = 0.0f; // "Dry signal level (dB)" input, control, -70 to 0, default -70
// controls[5] = 0.0f; // "Early reflection level (dB)" input, control, -70 to 0, default 0
// controls[6] = -15.0f; // "Tail level (dB)" input, control, -70 to 0, default -17.5
// multi-band EQ. Experiment to see if I only adjust one if the defaults hold out.
// file: mbeq_1197.so
// controls[3] = 12.0f; // 220 Hz
// controls[4] = 12.0f; // 440 Hz
// controls[8] = -25.0f; // "1250Hz gain" input, control, -70 to 30, default 0
// controls[9] = -12.0f;
runPlugin();
// copy data back out from pOutBuffer to inPacket.data
// pOut/inBuffer[0] has size BUF_SIZE/2
// Maybe the issue is here?
// For now, force "dual mono" since most plugins we are looking at are mono output anyway.
for (int n = 0; n+1 < BUF_SIZE; n += 2) {
pluginOutput16bitInterlaced[n] = (pOutBuffer[0][n/2] + 1) * mid; // L
pluginOutput16bitInterlaced[n+1] = (pOutBuffer[0][n/2] + 1) * mid; // Copy L
//pluginOutput16bitInterlaced[n+1] = (pOutBuffer[1][n/2] + 1) * mid; // R
}
// copyDataOut();
// CAREFUL, get the 16 bits of plugin output into the 8 bit inPacket format:
// To bypass the function temporarly, just comment out this final copy loop:
if(pluginOutputEnable)
{
for(int i=0; i < BUF_SIZE*2; i+=2)
{
inPacket.data[i] = pluginOutput16bitInterlaced[i/2] & 0x00ff;
inPacket.data[i+1] = (pluginOutput16bitInterlaced[i/2] & 0xff00) >> 8;
}
}
// --- End plugin code.
if (!ringBuf->try_write(inPacket))
{
qDebug(logAudio()) << "Buffer full! capacity:" << ringBuf->capacity() << "length" << ringBuf->size();
}
free(pluginOutput16bitInterlaced);
pluginMutex.unlock();
return;
}
void* audioHandler::dlopenLADSPA(const char *pcFilename, int iFlag)
{
char * pcBuffer;
const char * pcEnd;
const char * pcLADSPAPath;
const char * pcStart;
int iEndsInSO;
int iNeedSlash;
void* pvResult;
size_t iFilenameLength;
iFilenameLength = strlen(pcFilename);
pvResult = NULL;
if (pcFilename[0] == '/') {
/* The filename is absolute. Assume the user knows what he/she is
doing and simply dlopen() it. */
pvResult = dlopen(pcFilename, iFlag);
if (pvResult != NULL)
return pvResult;
}
else {
/* If the filename is not absolute then we wish to check along the
LADSPA_PATH path to see if we can find the file there. We do
NOT call dlopen() directly as this would find plugins on the
LD_LIBRARY_PATH, whereas the LADSPA_PATH is the correct place
to search. */
pcLADSPAPath = getenv("LADSPA_PATH");
if (pcLADSPAPath) {
pcStart = pcLADSPAPath;
while (*pcStart != '\0') {
pcEnd = pcStart;
while (*pcEnd != ':' && *pcEnd != '\0')
pcEnd++;
pcBuffer = (char*)malloc(iFilenameLength + (size_t)2 + (size_t)(pcEnd - pcStart));
if (pcEnd > pcStart)
strncpy(pcBuffer, pcStart, pcEnd - pcStart);
iNeedSlash = 0;
if (pcEnd > pcStart)
if (*(pcEnd - 1) != '/') {
iNeedSlash = 1;
pcBuffer[pcEnd - pcStart] = '/';
}
strcpy(pcBuffer + iNeedSlash + (pcEnd - pcStart), pcFilename);
pvResult = dlopen(pcBuffer, iFlag);
free(pcBuffer);
if (pvResult != NULL)
return pvResult;
pcStart = pcEnd;
if (*pcStart == ':')
pcStart++;
}
}
}
/* As a last ditch effort, check if filename does not end with
".so". In this case, add this suffix and recurse. */
iEndsInSO = 0;
if (iFilenameLength > 3)
iEndsInSO = (strcmp(pcFilename + iFilenameLength - 3, ".so") == 0);
if (!iEndsInSO) {
pcBuffer = (char*)malloc(iFilenameLength + 4);
strcpy(pcBuffer, pcFilename);
strcat(pcBuffer, ".so");
pvResult = dlopenLADSPA(pcBuffer, iFlag);
free(pcBuffer);
}
if (pvResult != NULL)
return pvResult;
/* If nothing has worked, then at least we can make sure we set the
correct error message - and this should correspond to a call to
dlopen() with the actual filename requested. The dlopen() manual
page does not specify whether the first or last error message
will be kept when multiple calls are made to dlopen(). We've
covered the former case - now we can handle the latter by calling
dlopen() again here. */
return dlopen(pcFilename, iFlag);
}
void* audioHandler::loadLADSPAPluginLibrary(const char *pcPluginFilename)
{
void * pvPluginHandle;
pvPluginHandle = dlopenLADSPA(pcPluginFilename, RTLD_NOW);
if (!pvPluginHandle) {
qInfo(logAudio()) << QString("Failed to load plugin %1: %2").arg(pcPluginFilename).arg(dlerror());
return NULL;
}
return pvPluginHandle;
}
void unloadLADSPAPluginLibrary(void *pvLADSPAPluginLibrary)
{
dlclose(pvLADSPAPluginLibrary);
}
const LADSPA_Descriptor* audioHandler::findLADSPAPluginDescriptor(void *pvLADSPAPluginLibrary,
const char *pcPluginLibraryFilename,
const char *pcPluginLabel)
{
const LADSPA_Descriptor * psDescriptor;
LADSPA_Descriptor_Function pfDescriptorFunction;
unsigned long lPluginIndex;
dlerror();
pfDescriptorFunction
= (LADSPA_Descriptor_Function)dlsym(pvLADSPAPluginLibrary,
"ladspa_descriptor");
if (!pfDescriptorFunction) {
const char * pcError = dlerror();
if (pcError) {
qInfo(logAudio()) <<
QString("Unable to find ladspa_descriptor() function in plugin library file %1: %2. Are you sure this is a LADSPA plugin file?").arg(pcPluginLibraryFilename).arg(pcError);
return NULL;
}
}
for (lPluginIndex = 0;; lPluginIndex++) {
psDescriptor = pfDescriptorFunction(lPluginIndex);
if (psDescriptor == NULL) {
qInfo(logAudio()) << QString("Unable to find label %1: %2").arg(pcPluginLabel).arg(pcPluginLibraryFilename);
return NULL;
}
if (strcmp(psDescriptor->Label, pcPluginLabel) == 0)
return psDescriptor;
}
}
void audioHandler::changeLatency(const quint16 newSize)
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency;
setup.latency = newSize;
}
int audioHandler::getLatency()
{
return currentLatency;
}
void audioHandler::getNextAudioChunk(QByteArray& ret)
{
audioPacket packet;
packet.sent = 0;
if (isInitialized && ringBuf != Q_NULLPTR && ringBuf->try_read(packet))
{
currentLatency = packet.time.msecsTo(QTime::currentTime());
while (currentLatency > setup.latency) {
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Packet " << hex << packet.seq <<
" arrived too late (increase output latency!) " <<
dec << packet.time.msecsTo(QTime::currentTime()) << "ms";
if (!ringBuf->try_read(packet))
break;
currentLatency = packet.time.msecsTo(QTime::currentTime());
}
//qDebug(logAudio) << "Chunksize" << this->chunkSize << "Packet size" << packet.data.length();
// Packet will arrive as stereo interleaved 16bit 48K
if (ratioNum != 1)
{
quint32 outFrames = ((packet.data.length() / 2 / devChannels) / ratioNum);
quint32 inFrames = (packet.data.length() / 2 / devChannels);
QByteArray outPacket((int)outFrames * 2 * devChannels, (char)0xff);
const qint16* in = (qint16*)packet.data.constData();
qint16* out = (qint16*)outPacket.data();
int err = 0;
err = wf_resampler_process_interleaved_int(resampler, in, &inFrames, out, &outFrames);
if (err) {
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Resampler error " << err << " inFrames:" << inFrames << " outFrames:" << outFrames;
}
//qInfo(logAudio()) << "Resampler run " << err << " inFrames:" << inFrames << " outFrames:" << outFrames;
//qInfo(logAudio()) << "Resampler run inLen:" << packet->datain.length() << " outLen:" << packet->dataout.length();
packet.data.clear();
packet.data = outPacket; // Copy output packet back to input buffer.
}
//qDebug(logAudio()) << "Now resampled, length" << packet.data.length();
// Do we need to convert mono to stereo?
if (setup.radioChan == 1 && devChannels > 1)
{
// Strip out right channel?
QByteArray outPacket(packet.data.length()/2, (char)0xff);
const qint16* in = (qint16*)packet.data.constData();
qint16* out = (qint16*)outPacket.data();
for (int f = 0; f < outPacket.length()/2; f++)
{
*out++ = *in++;
in++; // Skip each even channel.
}
packet.data.clear();
packet.data = outPacket; // Copy output packet back to input buffer.
}
//qDebug(logAudio()) << "Now mono, length" << packet.data.length();
// Do we need to convert 16-bit to 8-bit?
if (setup.bits == 8) {
QByteArray outPacket((int)packet.data.length() / 2, (char)0xff);
qint16* in = (qint16*)packet.data.data();
for (int f = 0; f < outPacket.length(); f++)
{
quint8 outdata = 0;
if (isUlaw) {
qint16 enc = qFromLittleEndian<quint16>(in + f);
if (enc >= 0)
outdata = ulaw_encode[enc];
else
outdata = 0x7f & ulaw_encode[-enc];
}
else {
outdata = (quint8)(((qFromLittleEndian<qint16>(in + f) >> 8) ^ 0x80) & 0xff);
}
outPacket[f] = (char)outdata;
}
packet.data.clear();
packet.data = outPacket; // Copy output packet back to input buffer.
}
ret = packet.data;
//qDebug(logAudio()) << "Now radio format, length" << packet.data.length();
}
return;
}
#if !defined (RTAUDIO) && !defined(PORTAUDIO)
qint64 audioHandler::bytesAvailable() const
{
return 0;
}
bool audioHandler::isSequential() const
{
return true;
}
void audioHandler::notified()
{
}
void audioHandler::stateChanged(QAudio::State state)
{
// Process the state
switch (state)
{
case QAudio::IdleState:
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio now in idle state: " << audioBuffer.size() << " packets in buffer";
if (audioOutput != Q_NULLPTR && audioOutput->error() == QAudio::UnderrunError)
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "buffer underrun";
//audioOutput->suspend();
}
break;
}
case QAudio::ActiveState:
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio now in active state: " << audioBuffer.size() << " packets in buffer";
break;
}
case QAudio::SuspendedState:
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio now in suspended state: " << audioBuffer.size() << " packets in buffer";
break;
}
case QAudio::StoppedState:
{
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Audio now in stopped state: " << audioBuffer.size() << " packets in buffer";
break;
}
default: {
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unhandled audio state: " << audioBuffer.size() << " packets in buffer";
}
}
}
void audioHandler::stop()
{
if (audioOutput != Q_NULLPTR && audioOutput->state() != QAudio::StoppedState) {
// Stop audio output
audioOutput->stop();
this->stop();
this->close();
delete audioOutput;
audioOutput = Q_NULLPTR;
}
if (audioInput != Q_NULLPTR && audioInput->state() != QAudio::StoppedState) {
// Stop audio output
audioInput->stop();
this->stop();
this->close();
delete audioInput;
audioInput = Q_NULLPTR;
}
isInitialized = false;
}
#endif