2022-11-05 05:44:10 +00:00
# include "audiooutput.h"
# include <QAudioFormat>
# include <QDebug>
# include <QtConcurrent/qtconcurrentrun.h>
# include <QFuture>
# include <QWaitCondition>
# include <QCoreApplication>
# include <QThreadPool>
# if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
# include <QAudioOutput>
# else
# include <QAudioSink>
# endif
# include <QtGlobal>
# include <QBuffer>
2022-11-12 09:26:10 +00:00
// take reference from this file (https://github.com/valbok/QtAVPlayer/blob/6cc30e484b354d59511c9a60fabced4cb7c57c8e/src/QtAVPlayer/qavaudiooutput.cpp)
// and make some changes.
2022-11-05 05:44:10 +00:00
static QAudioFormat format ( int sampleRate , int channelCount )
{
QAudioFormat out ;
out . setSampleRate ( sampleRate ) ;
2022-11-15 11:54:34 +00:00
out . setChannelCount ( channelCount ) ;
2022-11-05 05:44:10 +00:00
# if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
out . setByteOrder ( QAudioFormat : : LittleEndian ) ;
out . setCodec ( QLatin1String ( " audio/pcm " ) ) ;
# endif
# if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
out . setSampleSize ( 16 ) ;
out . setSampleType ( QAudioFormat : : SignedInt ) ;
# else
out . setSampleFormat ( QAudioFormat : : Int16 ) ;
# endif
return out ;
}
class AudioOutputPrivate : public QIODevice
{
public :
AudioOutputPrivate ( )
{
open ( QIODevice : : ReadOnly ) ;
threadPool . setMaxThreadCount ( 1 ) ;
}
QFuture < void > audioPlayFuture ;
# if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
using AudioOutput = QAudioOutput ;
# else
using AudioOutput = QAudioSink ;
# endif
AudioOutput * audioOutput = nullptr ;
QByteArray buffer ;
qint64 offset = 0 ;
bool quit = 0 ;
QMutex mutex ;
QWaitCondition cond ;
QThreadPool threadPool ;
int sampleRate = 0 ;
int channels = 0 ;
void setAudioFormat ( int _sampleRate , int _channels )
{
sampleRate = _sampleRate ;
channels = _channels ;
}
qint64 readData ( char * data , qint64 len ) override
{
if ( ! len )
return 0 ;
QMutexLocker locker ( & mutex ) ;
qint64 bytesWritten = 0 ;
while ( len & & ! quit )
{
if ( buffer . isEmpty ( ) )
{
// Wait for more frames
if ( bytesWritten = = 0 )
cond . wait ( & mutex ) ;
if ( buffer . isEmpty ( ) )
break ;
}
auto sampleData = buffer . data ( ) ;
const int toWrite = qMin ( ( qint64 ) buffer . size ( ) , len ) ;
memcpy ( & data [ bytesWritten ] , sampleData , toWrite ) ;
buffer . remove ( 0 , toWrite ) ;
bytesWritten + = toWrite ;
// data += toWrite;
len - = toWrite ;
}
return bytesWritten ;
}
qint64 writeData ( const char * , qint64 ) override { return 0 ; }
qint64 size ( ) const override { return buffer . size ( ) ; }
qint64 bytesAvailable ( ) const override { return buffer . size ( ) ; }
bool isSequential ( ) const override { return true ; }
bool atEnd ( ) const override { return buffer . isEmpty ( ) ; }
void init ( const QAudioFormat & fmt )
{
if ( ! audioOutput | | ( fmt . isValid ( ) & & audioOutput - > format ( ) ! = fmt )
| | audioOutput - > state ( ) = = QAudio : : StoppedState )
{
if ( audioOutput )
audioOutput - > deleteLater ( ) ;
audioOutput = new AudioOutput ( fmt ) ;
QObject : : connect ( audioOutput , & AudioOutput : : stateChanged , audioOutput , [ & ] ( QAudio : : State state ) {
switch ( state )
{
case QAudio : : StoppedState :
if ( audioOutput - > error ( ) ! = QAudio : : NoError )
2023-02-07 15:09:55 +00:00
{
2022-11-05 05:44:10 +00:00
qWarning ( ) < < " QAudioOutput stopped: " < < audioOutput - > error ( ) ;
2023-02-07 15:09:55 +00:00
quit = true ;
}
2022-11-05 05:44:10 +00:00
break ;
default :
break ;
}
} ) ;
audioOutput - > start ( this ) ;
2023-02-07 15:09:55 +00:00
if ( audioOutput & & audioOutput - > state ( ) = = QAudio : : StoppedState )
quit = true ;
2022-11-05 05:44:10 +00:00
}
// audioOutput->setVolume(volume);
}
void doPlayAudio ( )
{
while ( ! quit )
{
QMutexLocker locker ( & mutex ) ;
cond . wait ( & mutex , 10 ) ;
auto fmt = sampleRate = = 0 ? QAudioFormat ( ) : format ( sampleRate , channels ) ;
locker . unlock ( ) ;
if ( fmt . isValid ( ) )
init ( fmt ) ;
QCoreApplication : : processEvents ( ) ;
}
if ( audioOutput )
{
audioOutput - > stop ( ) ;
audioOutput - > deleteLater ( ) ;
}
audioOutput = nullptr ;
}
} ;
AudioOutput : : AudioOutput ( QObject * parent ) : QObject ( parent ) , d_ptr ( new AudioOutputPrivate )
{
# if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
d_ptr - > audioPlayFuture = QtConcurrent : : run ( & d_ptr - > threadPool , d_ptr . data ( ) , & AudioOutputPrivate : : doPlayAudio ) ;
# else
d_ptr - > audioPlayFuture = QtConcurrent : : run ( & d_ptr - > threadPool , & AudioOutputPrivate : : doPlayAudio , d_ptr . data ( ) ) ;
# endif
}
void AudioOutput : : setAudioFormat ( int sampleRate , int channels ) { d_ptr - > setAudioFormat ( sampleRate , channels ) ; }
AudioOutput : : ~ AudioOutput ( )
{
Q_D ( AudioOutput ) ;
d - > quit = true ;
d - > cond . wakeAll ( ) ;
d - > audioPlayFuture . waitForFinished ( ) ;
}
bool AudioOutput : : play ( const uint8_t * data , qint64 len )
{
Q_D ( AudioOutput ) ;
if ( d - > quit )
return false ;
QMutexLocker locker ( & d - > mutex ) ;
auto cuint = const_cast < uint8_t * > ( data ) ;
auto cptr = reinterpret_cast < char * > ( cuint ) ;
d - > buffer . append ( cptr , len ) ;
d - > cond . wakeAll ( ) ;
return true ;
}