mirror of
https://github.com/wf-group/wfview
synced 2026-08-13 17:32:59 -04:00
226 lines
6.4 KiB
C++
226 lines
6.4 KiB
C++
#ifndef AUDIOCONVERTER_H
|
|
#define AUDIOCONVERTER_H
|
|
|
|
#include <QObject>
|
|
#include <QByteArray>
|
|
#include <QTime>
|
|
#include <QMap>
|
|
#include <QDebug>
|
|
#include <QAudioFormat>
|
|
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
#include <QAudioDeviceInfo>
|
|
#include <QAudioInput>
|
|
#include <QAudioOutput>
|
|
#else
|
|
#include <QMediaDevices>
|
|
#include <QAudioDevice>
|
|
#include <QAudioSource>
|
|
#include <QAudioSink>
|
|
#endif
|
|
|
|
/* Opus and Eigen */
|
|
#ifndef Q_OS_LINUX
|
|
#include "opus.h"
|
|
#include <Eigen/Eigen>
|
|
#include <Eigen/Dense>
|
|
#else
|
|
#include "opus/opus.h"
|
|
#include <eigen3/Eigen/Eigen>
|
|
#include <eigen3/Eigen/Dense>
|
|
#endif
|
|
|
|
#include "wfviewtypes.h"
|
|
|
|
#include "resampler/speex_resampler.h"
|
|
|
|
#include "packettypes.h"
|
|
|
|
#include <functional>
|
|
|
|
using MapFUn = Eigen::Map<Eigen::VectorXf, Eigen::Unaligned>;
|
|
using MapI16Un = Eigen::Map<Eigen::Matrix<qint16, Eigen::Dynamic, 1>, Eigen::Unaligned>;
|
|
using MapI32Un = Eigen::Map<Eigen::Matrix<qint32, Eigen::Dynamic, 1>, Eigen::Unaligned>;
|
|
using MapU8Un = Eigen::Map<Eigen::Matrix<quint8, Eigen::Dynamic, 1>, Eigen::Unaligned>;
|
|
|
|
struct audioPacket {
|
|
quint32 seq;
|
|
QTime time;
|
|
quint16 sent;
|
|
QByteArray data;
|
|
quint8 guid[GUIDLEN];
|
|
float amplitudePeak;
|
|
float amplitudeRMS;
|
|
qreal volume = 1.0;
|
|
};
|
|
|
|
// Forward declarations
|
|
class TxAudioProcessor; // defined in txaudioprocessor.h
|
|
class RxAudioProcessor; // defined in rxaudioprocessor.h
|
|
|
|
struct audioSetup {
|
|
audioType type;
|
|
QString name;
|
|
quint16 latency;
|
|
quint8 codec;
|
|
bool ulaw = false;
|
|
bool isinput;
|
|
quint32 sampleRate;
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
QAudioDeviceInfo port;
|
|
#else
|
|
QAudioDevice port;
|
|
#endif
|
|
int portInt;
|
|
quint8 resampleQuality;
|
|
quint8 localAFgain;
|
|
quint16 blockSize = 20; // Each 'block' of audio is 20ms long by default.
|
|
quint8 guid[GUIDLEN];
|
|
void* tci = Q_NULLPTR;
|
|
// Optional TX processing engine; non-null only for TX input handlers.
|
|
TxAudioProcessor* txProc = nullptr;
|
|
// Optional RX processing engine; non-null only for RX output handlers.
|
|
RxAudioProcessor* rxProc = nullptr;
|
|
};
|
|
|
|
class audioConverter : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit audioConverter(QObject* parent = nullptr);;
|
|
~audioConverter();
|
|
|
|
public slots:
|
|
bool init(QAudioFormat inFormat, codecType inCodec, QAudioFormat outFormat, codecType outCodec, quint8 opusComplexity, quint8 resampleQuality);
|
|
bool convert(audioPacket audio);
|
|
// Install (or clear) a synchronous audio processing hook.
|
|
// The hook is called after resampling and before encoding, in the converter thread.
|
|
// Used for TX processing (mic→codec path, post-resample at codec rate).
|
|
void setProcessingHook(std::function<Eigen::VectorXf(Eigen::VectorXf)> hook);
|
|
|
|
// Install (or clear) a pre-resample processing hook.
|
|
// Called BEFORE channel conversion and resampling, at the codec's native rate.
|
|
// Used for RX processing (codec→device path, pre-resample at codec rate).
|
|
void setPreResampleHook(std::function<Eigen::VectorXf(Eigen::VectorXf)> hook);
|
|
|
|
signals:
|
|
void converted(audioPacket audio);
|
|
void floatAudio(Eigen::VectorXf audio);
|
|
|
|
protected:
|
|
QAudioFormat inFormat;
|
|
QAudioFormat outFormat;
|
|
OpusEncoder* opusEncoder = Q_NULLPTR;
|
|
OpusDecoder* opusDecoder = Q_NULLPTR;
|
|
SpeexResamplerState* resampler = Q_NULLPTR;
|
|
quint8 opusComplexity;
|
|
quint8 resampleQuality = 4;
|
|
double resampleRatio=1.0; // Default resample ratio is 1:1
|
|
quint32 lastAudioSequence;
|
|
codecType inCodec;
|
|
codecType outCodec;
|
|
void * adpcmContext = Q_NULLPTR;
|
|
QByteArray scratchIn;
|
|
QByteArray scratchOut;
|
|
Eigen::VectorXf scratchF;
|
|
|
|
// TX processing hook — called after resample (post-resample, at codec rate for TX)
|
|
std::function<Eigen::VectorXf(Eigen::VectorXf)> processingHook;
|
|
// RX processing hook — called before channel conversion and resample (at codec rate)
|
|
std::function<Eigen::VectorXf(Eigen::VectorXf)> preResampleHook;
|
|
};
|
|
|
|
|
|
// Various audio handling functions declared inline
|
|
|
|
typedef Eigen::Matrix<quint8, Eigen::Dynamic, 1> VectorXuint8;
|
|
typedef Eigen::Matrix<qint8, Eigen::Dynamic, 1> VectorXint8;
|
|
typedef Eigen::Matrix<qint16, Eigen::Dynamic, 1> VectorXint16;
|
|
typedef Eigen::Matrix<qint32, Eigen::Dynamic, 1> VectorXint32;
|
|
|
|
// Register VectorXf for queued cross-thread signal delivery
|
|
Q_DECLARE_METATYPE(Eigen::VectorXf)
|
|
|
|
static inline QAudioFormat toQAudioFormat(quint8 codec, quint32 sampleRate)
|
|
{
|
|
QAudioFormat format;
|
|
|
|
/*
|
|
0x01 uLaw 1ch 8bit
|
|
0x02 PCM 1ch 8bit
|
|
0x04 PCM 1ch 16bit
|
|
0x08 PCM 2ch 8bit
|
|
0x10 PCM 2ch 16bit
|
|
0x20 uLaw 2ch 8bit
|
|
0x40 Opus 1ch
|
|
0x41 Opus 2ch
|
|
0x80 ADPCM
|
|
*/
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
format.setByteOrder(QAudioFormat::LittleEndian);
|
|
format.setCodec("audio/pcm");
|
|
#endif
|
|
|
|
format.setSampleRate(sampleRate);
|
|
|
|
if (codec == 0x01 || codec == 0x20) {
|
|
/* Set sample to be what is expected by the encoder and the output of the decoder */
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
format.setSampleSize(32);
|
|
format.setSampleType(QAudioFormat::Float);
|
|
format.setCodec("audio/PCMU");
|
|
#else
|
|
format.setSampleFormat(QAudioFormat::Float);
|
|
#endif
|
|
}
|
|
|
|
if (codec == 0x02 || codec == 0x08) {
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
format.setSampleSize(8);
|
|
format.setSampleType(QAudioFormat::UnSignedInt);
|
|
#else
|
|
format.setSampleFormat(QAudioFormat::UInt8);
|
|
#endif
|
|
}
|
|
if (codec == 0x08 || codec == 0x10 || codec == 0x20 || codec == 0x80) {
|
|
format.setChannelCount(2);
|
|
} else {
|
|
format.setChannelCount(1);
|
|
}
|
|
|
|
if (codec == 0x04 || codec == 0x10) {
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
format.setSampleSize(16);
|
|
format.setSampleType(QAudioFormat::SignedInt);
|
|
#else
|
|
format.setSampleFormat(QAudioFormat::Int16);
|
|
#endif
|
|
}
|
|
|
|
if (codec == 0x40 || codec == 0x41) {
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
format.setSampleSize(32);
|
|
format.setSampleType(QAudioFormat::Float);
|
|
format.setCodec("audio/opus");
|
|
#else
|
|
format.setSampleFormat(QAudioFormat::Float);
|
|
#endif
|
|
}
|
|
|
|
if (codec == 0x80) {
|
|
/* Set sample to be what is expected by the encoder and the output of the decoder */
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
|
format.setSampleSize(16);
|
|
format.setSampleType(QAudioFormat::SignedInt);
|
|
format.setCodec("audio/ADPCM");
|
|
#else
|
|
format.setSampleFormat(QAudioFormat::Int16);
|
|
#endif
|
|
format.setChannelCount(1);
|
|
}
|
|
|
|
return format;
|
|
}
|
|
|
|
#endif
|