2022-05-12 00:46:20 +00:00
# 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 ( ) ;
}
2022-07-05 09:37:10 +00:00
if ( isInitialized ) {
Pa_StopStream ( audio ) ;
Pa_CloseStream ( audio ) ;
}
2022-05-12 00:46:20 +00:00
}
bool paHandler : : init ( audioSetup setup )
{
if ( isInitialized ) {
return false ;
}
this - > setup = setup ;
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " PortAudio handler starting: " < < setup . name ;
2022-05-13 11:35:28 +00:00
if ( setup . portInt = = - 1 )
2022-05-12 00:46:20 +00:00
{
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 < <
2022-12-29 15:26:41 +00:00
# if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
2022-05-12 00:46:20 +00:00
" , bits " < < inFormat . sampleSize ( ) < <
2022-08-22 22:09:09 +00:00
# else
" , format " < < inFormat . sampleFormat ( ) < <
# endif
2022-05-12 00:46:20 +00:00
" , 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";
//}
2022-08-22 22:09:09 +00:00
codecType codec = LPCM ;
if ( setup . codec = = 0x01 | | setup . codec = = 0x20 )
codec = PCMU ;
else if ( setup . codec = = 0x40 | | setup . codec = = 0x40 )
codec = OPUS ;
2022-05-12 00:46:20 +00:00
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 ) ;
}
2022-05-13 11:35:28 +00:00
aParams . suggestedLatency = ( float ) setup . latency / 1000.0f ;
2022-05-12 00:46:20 +00:00
outFormat . setSampleRate ( info - > defaultSampleRate ) ;
aParams . sampleFormat = paFloat32 ;
2022-08-22 22:09:09 +00:00
2022-12-29 15:26:41 +00:00
# if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
2022-05-12 00:46:20 +00:00
outFormat . setSampleSize ( 32 ) ;
outFormat . setSampleType ( QAudioFormat : : Float ) ;
outFormat . setByteOrder ( QAudioFormat : : LittleEndian ) ;
outFormat . setCodec ( " audio/pcm " ) ;
2022-08-22 22:09:09 +00:00
# else
outFormat . setSampleFormat ( QAudioFormat : : Float ) ;
# endif
2022-05-12 00:46:20 +00:00
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 ;
}
2023-01-02 17:56:22 +00:00
if ( outFormat . channelCount ( ) = = 1 & & inFormat . channelCount ( ) = = 2 ) {
outFormat . setChannelCount ( 2 ) ;
2022-05-12 00:46:20 +00:00
}
aParams . channelCount = outFormat . channelCount ( ) ;
if ( outFormat . sampleRate ( ) < 44100 ) {
outFormat . setSampleRate ( 48000 ) ;
}
2022-05-13 11:35:28 +00:00
2022-05-12 00:46:20 +00:00
2022-12-29 15:26:41 +00:00
# if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
2022-05-12 00:46:20 +00:00
qDebug ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Selected format: SampleSize " < < outFormat . sampleSize ( ) < < " Channel Count " < < outFormat . channelCount ( ) < <
2022-08-22 22:09:09 +00:00
" Sample Rate " < < outFormat . sampleRate ( ) < < " Codec " < < codec < < " Sample Type " < < outFormat . sampleType ( ) ;
# else
qDebug ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Selected format: SampleFormat " < < outFormat . sampleFormat ( ) < < " Channel Count " < < outFormat . channelCount ( ) < <
" Sample Rate " < < outFormat . sampleRate ( ) < < " Codec " < < codec ;
# endif
2022-05-12 00:46:20 +00:00
// 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 ) ;
2022-08-22 22:09:09 +00:00
connect ( this , SIGNAL ( setupConverter ( QAudioFormat , codecType , QAudioFormat , codecType , quint8 , quint8 ) ) , converter , SLOT ( init ( QAudioFormat , codecType , QAudioFormat , codecType , quint8 , quint8 ) ) ) ;
2022-05-12 00:46:20 +00:00
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.
2022-05-13 11:35:28 +00:00
this - > chunkSize = ( outFormat . bytesForDuration ( setup . blockSize * 1000 ) / sizeof ( float ) ) * outFormat . channelCount ( ) ;
// Check the format is supported
2022-05-12 00:46:20 +00:00
if ( setup . isinput ) {
2022-05-13 11:35:28 +00:00
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 ;
2022-12-29 15:26:41 +00:00
# if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
2022-05-13 11:35:28 +00:00
outFormat . setSampleType ( QAudioFormat : : SignedInt ) ;
outFormat . setSampleSize ( 16 ) ;
2022-08-22 22:09:09 +00:00
# else
outFormat . setSampleFormat ( QAudioFormat : : Int16 ) ;
# endif
2022-05-13 11:35:28 +00:00
}
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 ) {
2022-05-12 00:46:20 +00:00
err = Pa_OpenStream ( & audio , & aParams , 0 , outFormat . sampleRate ( ) , this - > chunkSize , paNoFlag , & paHandler : : staticWrite , ( void * ) this ) ;
2022-08-22 22:09:09 +00:00
emit setupConverter ( outFormat , codec , inFormat , codecType : : LPCM , 7 , setup . resampleQuality ) ;
2022-05-12 00:46:20 +00:00
connect ( converter , SIGNAL ( converted ( audioPacket ) ) , this , SLOT ( convertedInput ( audioPacket ) ) ) ;
}
else {
err = Pa_OpenStream ( & audio , 0 , & aParams , outFormat . sampleRate ( ) , this - > chunkSize , paNoFlag , NULL , NULL ) ;
2022-08-22 22:09:09 +00:00
emit setupConverter ( inFormat , codec , outFormat , codecType : : LPCM , 7 , setup . resampleQuality ) ;
2022-05-12 00:46:20 +00:00
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 ) ;
}
2022-05-13 08:55:16 +00:00
this - > setVolume ( setup . localAFgain ) ;
2022-05-12 00:46:20 +00:00
return isInitialized ;
}
void paHandler : : setVolume ( unsigned char volume )
{
2022-05-13 15:09:26 +00:00
# ifdef Q_OS_WIN
this - > volume = audiopot [ volume ] * 5 ;
# else
2022-05-12 00:46:20 +00:00
this - > volume = audiopot [ volume ] ;
2022-05-13 15:09:26 +00:00
# endif
2022-05-12 00:46:20 +00:00
}
void paHandler : : incomingAudio ( audioPacket packet )
{
packet . volume = volume ;
2022-12-01 13:16:05 +00:00
if ( Pa_IsStreamActive ( audio ) = = 1 ) {
emit sendToConverter ( packet ) ;
}
else
{
Pa_StartStream ( audio ) ;
}
2022-05-12 00:46:20 +00:00
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 ) ;
2022-05-13 15:09:26 +00:00
if ( status = = paInputUnderflow ) {
isUnderrun = true ;
}
else if ( status = = paInputOverflow ) {
isOverrun = true ;
}
else
{
isUnderrun = false ;
isOverrun = false ;
}
2022-05-12 00:46:20 +00:00
return paContinue ;
}
void paHandler : : convertedOutput ( audioPacket packet ) {
if ( packet . data . size ( ) > 0 ) {
if ( Pa_IsStreamActive ( audio ) = = 1 ) {
2022-12-01 13:16:05 +00:00
if ( currentLatency < ( setup . latency + latencyAllowance ) ) {
2022-12-01 13:08:29 +00:00
PaError err = Pa_WriteStream ( audio , ( char * ) packet . data . data ( ) , packet . data . size ( ) / sizeof ( float ) / outFormat . channelCount ( ) ) ;
2022-05-12 00:46:20 +00:00
2022-12-01 13:08:29 +00:00
if ( err ! = paNoError ) {
qDebug ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Error writing audio! " ;
}
}
else {
2023-01-02 20:07:34 +00:00
qDebug ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Discarding audio data as current latency " < < currentLatency < < " exceeds setup latency " < < setup . latency ;
2022-12-01 13:16:05 +00:00
Pa_StopStream ( audio ) ;
latencyAllowance + + ;
2022-05-12 00:46:20 +00:00
}
const PaStreamInfo * info = Pa_GetStreamInfo ( audio ) ;
2022-05-13 15:09:26 +00:00
currentLatency = packet . time . msecsTo ( QTime : : currentTime ( ) ) + ( info - > outputLatency * 1000 ) ;
2022-05-12 00:46:20 +00:00
}
2022-08-24 05:24:05 +00:00
amplitude = packet . amplitudePeak ;
2022-08-24 22:02:00 +00:00
emit haveLevels ( getAmplitude ( ) , static_cast < quint16 > ( packet . amplitudeRMS * 255.0 ) , setup . latency , currentLatency , isUnderrun , isOverrun ) ;
2022-05-12 00:46:20 +00:00
}
}
2022-05-13 15:09:26 +00:00
void paHandler : : convertedInput ( audioPacket packet )
2022-05-12 00:46:20 +00:00
{
2022-05-13 15:09:26 +00:00
if ( packet . data . size ( ) > 0 ) {
emit haveAudioData ( packet ) ;
2022-08-24 05:24:05 +00:00
amplitude = packet . amplitudePeak ;
2022-05-13 15:09:26 +00:00
const PaStreamInfo * info = Pa_GetStreamInfo ( audio ) ;
currentLatency = packet . time . msecsTo ( QTime : : currentTime ( ) ) + ( info - > inputLatency * 1000 ) ;
2022-08-24 22:02:00 +00:00
emit haveLevels ( getAmplitude ( ) , packet . amplitudeRMS , setup . latency , currentLatency , isUnderrun , isOverrun ) ;
2022-05-12 00:46:20 +00:00
}
}
void paHandler : : changeLatency ( const quint16 newSize )
{
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Changing latency to: " < < newSize < < " from " < < setup . latency ;
2022-12-01 13:08:29 +00:00
setup . latency = newSize ;
latencyAllowance = 0 ;
2022-05-12 00:46:20 +00:00
}
int paHandler : : getLatency ( )
{
return currentLatency ;
}
quint16 paHandler : : getAmplitude ( )
{
2022-05-14 00:11:00 +00:00
return static_cast < quint16 > ( amplitude * 255.0 ) ;
2022-05-12 00:46:20 +00:00
}