2022-05-12 00:46:20 +00:00
# include "rthandler.h"
# include "logcategories.h"
# if defined(Q_OS_WIN)
# include <objbase.h>
# endif
2023-01-01 21:40:46 +00:00
# undef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
rtHandler : : rtHandler ( QObject * parent )
{
Q_UNUSED ( parent )
}
rtHandler : : ~ rtHandler ( )
{
2022-07-05 09:37:10 +00:00
if ( converterThread ! = Q_NULLPTR ) {
converterThread - > quit ( ) ;
converterThread - > wait ( ) ;
}
2022-05-12 00:46:20 +00:00
if ( isInitialized ) {
2022-07-05 09:37:10 +00:00
2022-05-20 14:50:47 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
try {
2022-05-20 14:50:47 +00:00
# endif
2022-05-12 00:46:20 +00:00
audio - > abortStream ( ) ;
audio - > closeStream ( ) ;
2022-05-20 14:50:47 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
}
2022-05-14 14:05:48 +00:00
catch ( RtAudioError & e ) {
2022-05-12 00:46:20 +00:00
qInfo ( logAudio ( ) ) < < " Error closing stream: " < < aParams . deviceId < < " : " < < QString : : fromStdString ( e . getMessage ( ) ) ;
}
2022-05-20 14:50:47 +00:00
# endif
2022-05-12 00:46:20 +00:00
delete audio ;
}
}
bool rtHandler : : init ( audioSetup setup )
{
if ( isInitialized ) {
return false ;
}
this - > setup = setup ;
2022-05-14 13:02:31 +00:00
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " RTAudio handler starting: " < < setup . name ;
2022-05-12 00:46:20 +00:00
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 < <
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 ;
# if !defined(Q_OS_MACX)
2022-05-13 09:20:17 +00:00
options . flags = ( ( ! RTAUDIO_HOG_DEVICE ) | ( RTAUDIO_MINIMIZE_LATENCY ) ) ;
2022-05-12 00:46:20 +00:00
//options.flags = 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
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 11:24:27 +00:00
options . numberOfBuffers = int ( setup . latency / setup . blockSize ) ;
2022-05-12 00:46:20 +00:00
if ( setup . portInt > 0 ) {
aParams . deviceId = setup . portInt ;
}
else if ( setup . isinput ) {
aParams . deviceId = audio - > getDefaultInputDevice ( ) ;
}
else {
aParams . deviceId = audio - > getDefaultOutputDevice ( ) ;
}
aParams . firstChannel = 0 ;
2022-05-20 14:50:47 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
try {
2022-05-20 14:50:47 +00:00
# endif
2022-05-12 00:46:20 +00:00
info = audio - > getDeviceInfo ( aParams . deviceId ) ;
2022-05-20 14:50:47 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
}
2022-05-14 14:05:48 +00:00
catch ( RtAudioError e ) {
2022-05-14 09:33:31 +00:00
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Device exception: " < < aParams . deviceId < < " : " < < QString : : fromStdString ( e . getMessage ( ) ) ;
2022-05-14 09:41:44 +00:00
goto errorHandler ;
2022-05-12 00:46:20 +00:00
}
2022-05-20 14:50:47 +00:00
# endif
2023-01-01 21:40:46 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
if ( info . probed )
{
2023-01-01 21:40:46 +00:00
# endif
2022-05-12 00:46:20 +00:00
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < QString : : fromStdString ( info . name ) < < " ( " < < aParams . deviceId < < " ) successfully probed " ;
RtAudioFormat sampleFormat ;
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 . setByteOrder ( QAudioFormat : : LittleEndian ) ;
outFormat . setCodec ( " audio/pcm " ) ;
2022-08-22 22:09:09 +00:00
# endif
2022-05-12 00:46:20 +00:00
if ( info . nativeFormats = = 0 )
{
qCritical ( logAudio ( ) ) < < " No natively supported data formats! " ;
2022-05-14 09:41:44 +00:00
goto errorHandler ;
2022-05-12 00:46:20 +00:00
}
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 ) {
outFormat . setChannelCount ( info . inputChannels ) ;
}
else {
outFormat . setChannelCount ( info . outputChannels ) ;
}
qInfo ( logAudio ( ) ) < < " Channels: " < < outFormat . channelCount ( ) ;
if ( outFormat . channelCount ( ) > 2 ) {
outFormat . setChannelCount ( 2 ) ;
}
else if ( outFormat . channelCount ( ) < 1 )
{
qCritical ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " No channels found, aborting setup. " ;
2022-05-14 09:41:44 +00:00
goto errorHandler ;
2022-05-12 00:46:20 +00:00
}
2023-01-02 20:07:34 +00:00
if ( outFormat . channelCount ( ) = = 1 & & inFormat . channelCount ( ) = = 2 ) {
outFormat . setChannelCount ( 2 ) ;
}
2022-05-12 00:46:20 +00:00
aParams . nChannels = outFormat . channelCount ( ) ;
outFormat . setSampleRate ( info . preferredSampleRate ) ;
if ( outFormat . sampleRate ( ) < 44100 ) {
outFormat . setSampleRate ( 48000 ) ;
}
if ( info . nativeFormats & RTAUDIO_FLOAT32 ) {
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 . setSampleType ( QAudioFormat : : Float ) ;
outFormat . setSampleSize ( 32 ) ;
2022-08-22 22:09:09 +00:00
# else
outFormat . setSampleFormat ( QAudioFormat : : Float ) ;
# endif
2022-05-12 00:46:20 +00:00
sampleFormat = RTAUDIO_FLOAT32 ;
}
else if ( info . nativeFormats & RTAUDIO_SINT32 ) {
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 . setSampleType ( QAudioFormat : : SignedInt ) ;
outFormat . setSampleSize ( 32 ) ;
2022-08-22 22:09:09 +00:00
# else
outFormat . setSampleFormat ( QAudioFormat : : Int32 ) ;
# endif
2022-05-12 00:46:20 +00:00
sampleFormat = RTAUDIO_SINT32 ;
}
else if ( info . nativeFormats & RTAUDIO_SINT16 ) {
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 . setSampleType ( QAudioFormat : : SignedInt ) ;
outFormat . setSampleSize ( 16 ) ;
2022-08-22 22:09:09 +00:00
# else
outFormat . setSampleFormat ( QAudioFormat : : Int16 ) ;
# endif
2022-05-12 00:46:20 +00:00
sampleFormat = RTAUDIO_SINT16 ;
}
else {
qCritical ( logAudio ( ) ) < < " Cannot find supported sample format! " ;
2022-05-14 09:41:44 +00:00
goto errorHandler ;
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 ( ) < <
" Sample Rate " < < outFormat . sampleRate ( ) < < " Codec " < < outFormat . codec ( ) < < " Sample Type " < < outFormat . sampleType ( ) ;
2022-08-22 22:09:09 +00:00
# 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 ) ;
// Per channel chunk size.
2023-01-02 20:07:34 +00:00
this - > chunkSize = ( outFormat . bytesForDuration ( setup . blockSize * 1000 ) / outFormat . bytesPerFrame ( ) ) ;
2022-05-12 00:46:20 +00:00
2022-05-20 14:50:47 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
try {
2022-05-20 14:50:47 +00:00
# endif
2022-05-12 00:46:20 +00:00
if ( setup . isinput ) {
audio - > openStream ( NULL , & aParams , sampleFormat , outFormat . sampleRate ( ) , & this - > chunkSize , & staticWrite , this , & options ) ;
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 {
audio - > openStream ( & aParams , NULL , sampleFormat , outFormat . sampleRate ( ) , & this - > chunkSize , & staticRead , this , & options ) ;
2022-08-22 22:09:09 +00:00
emit setupConverter ( inFormat , codecType : : LPCM , outFormat , codec , 7 , setup . resampleQuality ) ;
2022-05-12 00:46:20 +00:00
connect ( converter , SIGNAL ( converted ( audioPacket ) ) , this , SLOT ( convertedOutput ( audioPacket ) ) ) ;
}
audio - > startStream ( ) ;
isInitialized = true ;
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " device successfully opened " ;
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " detected latency: " < < audio - > getStreamLatency ( ) ;
2022-05-20 14:50:47 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
}
catch ( RtAudioError & e ) {
qInfo ( logAudio ( ) ) < < " Error opening: " < < QString : : fromStdString ( e . getMessage ( ) ) ;
2022-05-14 00:27:44 +00:00
// Try again?
2022-05-14 09:41:44 +00:00
goto errorHandler ;
2022-05-12 00:46:20 +00:00
}
2022-05-20 14:50:47 +00:00
# endif
2023-01-01 21:40:46 +00:00
# ifdef RT_EXCEPTION
2022-05-12 00:46:20 +00:00
}
else
{
2022-05-14 09:41:44 +00:00
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < QString : : fromStdString ( info . name ) < < " ( " < < aParams . deviceId < < " ) could not be probed, check audio configuration! " ;
goto errorHandler ;
2022-05-12 00:46:20 +00:00
}
2023-01-01 21:40:46 +00:00
# endif
2022-05-13 08:55:16 +00:00
this - > setVolume ( setup . localAFgain ) ;
2022-05-12 00:46:20 +00:00
return isInitialized ;
2022-05-14 09:41:44 +00:00
errorHandler :
if ( retryConnectCount < 10 ) {
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " *** Attempting to reconnect to audio device in 500ms " ;
QTimer : : singleShot ( 500 , this , std : : bind ( & rtHandler : : init , this , setup ) ) ;
retryConnectCount + + ;
}
else
{
qCritical ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " *** Retry count exceeded, giving up! " ;
}
return false ;
2022-05-12 00:46:20 +00:00
}
void rtHandler : : setVolume ( unsigned char volume )
{
this - > volume = audiopot [ volume ] ;
}
void rtHandler : : incomingAudio ( audioPacket packet )
{
packet . volume = volume ;
emit sendToConverter ( packet ) ;
return ;
}
int rtHandler : : readData ( void * outputBuffer , void * inputBuffer ,
unsigned int nFrames , double streamTime , RtAudioStreamStatus status )
{
Q_UNUSED ( inputBuffer ) ;
Q_UNUSED ( streamTime ) ;
2023-01-02 20:07:34 +00:00
int nBytes = nFrames * outFormat . bytesPerFrame ( ) ;
2022-05-12 00:46:20 +00:00
//lastSentSeq = packet.seq;
if ( arrayBuffer . length ( ) > = nBytes ) {
2022-05-12 19:30:51 +00:00
if ( audioMutex . tryLock ( 0 ) ) {
std : : memcpy ( outputBuffer , arrayBuffer . constData ( ) , nBytes ) ;
arrayBuffer . remove ( 0 , nBytes ) ;
audioMutex . unlock ( ) ;
}
2022-05-12 00:46:20 +00:00
}
if ( status = = RTAUDIO_INPUT_OVERFLOW ) {
isUnderrun = true ;
}
else if ( status = = RTAUDIO_OUTPUT_UNDERFLOW ) {
isOverrun = true ;
}
else
{
isUnderrun = false ;
isOverrun = false ;
}
return 0 ;
}
int rtHandler : : writeData ( void * outputBuffer , void * inputBuffer ,
unsigned int nFrames , double streamTime , RtAudioStreamStatus 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 ) ;
2023-01-02 20:07:34 +00:00
packet . data . append ( ( char * ) inputBuffer , nFrames * outFormat . bytesPerFrame ( ) ) ;
2022-05-12 00:46:20 +00:00
emit sendToConverter ( packet ) ;
if ( status = = RTAUDIO_INPUT_OVERFLOW ) {
isUnderrun = true ;
}
else if ( status = = RTAUDIO_OUTPUT_UNDERFLOW ) {
isOverrun = true ;
}
else
{
isUnderrun = false ;
isOverrun = false ;
}
return 0 ;
}
void rtHandler : : convertedOutput ( audioPacket packet )
{
2022-05-12 19:30:51 +00:00
audioMutex . lock ( ) ;
2022-05-12 00:46:20 +00:00
arrayBuffer . append ( packet . data ) ;
2022-05-12 19:30:51 +00:00
audioMutex . unlock ( ) ;
2022-12-29 14:16:22 +00:00
amplitude = packet . amplitudePeak ;
2023-01-02 20:07:34 +00:00
currentLatency = packet . time . msecsTo ( QTime : : currentTime ( ) ) + ( outFormat . durationForBytes ( audio - > getStreamLatency ( ) * outFormat . bytesPerFrame ( ) ) / 1000 ) ;
2022-12-29 14:16:22 +00:00
emit haveLevels ( getAmplitude ( ) , packet . amplitudeRMS , setup . latency , currentLatency , isUnderrun , isOverrun ) ;
2022-05-12 00:46:20 +00:00
}
2022-05-13 09:20:17 +00:00
void rtHandler : : convertedInput ( audioPacket packet )
2022-05-12 00:46:20 +00:00
{
2022-05-13 09:20:17 +00:00
if ( packet . data . size ( ) > 0 ) {
emit haveAudioData ( packet ) ;
2022-12-29 14:16:22 +00:00
amplitude = packet . amplitudePeak ;
2023-01-02 20:07:34 +00:00
currentLatency = packet . time . msecsTo ( QTime : : currentTime ( ) ) + ( outFormat . durationForBytes ( audio - > getStreamLatency ( ) * outFormat . bytesPerFrame ( ) ) / 1000 ) ;
2022-12-29 14:16:22 +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
}
}
void rtHandler : : changeLatency ( const quint16 newSize )
{
qInfo ( logAudio ( ) ) < < ( setup . isinput ? " Input " : " Output " ) < < " Changing latency to: " < < newSize < < " from " < < setup . latency ;
}
int rtHandler : : getLatency ( )
{
return currentLatency ;
}
quint16 rtHandler : : getAmplitude ( )
{
2022-05-14 00:11:00 +00:00
return static_cast < quint16 > ( amplitude * 255.0 ) ;
2022-05-12 00:46:20 +00:00
}