File indexing completed on 2025-01-19 03:57:03
0001 /********************************************************* 0002 * Copyright (C) 2021, Val Doroshchuk <valbok@gmail.com> * 0003 * * 0004 * This file is part of QtAVPlayer. * 0005 * Free Qt Media Player based on FFmpeg. * 0006 *********************************************************/ 0007 0008 #include "qavaudiooutput.h" 0009 #include <QDebug> 0010 #include <QtConcurrent/qtconcurrentrun.h> 0011 #include <QFuture> 0012 #include <QWaitCondition> 0013 #include <QCoreApplication> 0014 #include <QThreadPool> 0015 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0016 #include <QAudioOutput> 0017 #else 0018 #include <QAudioSink> 0019 #include <QMediaDevices> 0020 #endif 0021 0022 extern "C" { 0023 #include "libavutil/time.h" 0024 } 0025 0026 QT_BEGIN_NAMESPACE 0027 0028 static QAudioFormat format(const QAVAudioFormat &from) 0029 { 0030 QAudioFormat out; 0031 0032 out.setSampleRate(from.sampleRate()); 0033 out.setChannelCount(from.channelCount()); 0034 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0035 out.setByteOrder(QAudioFormat::LittleEndian); 0036 out.setCodec(QLatin1String("audio/pcm")); 0037 #endif 0038 switch (from.sampleFormat()) { 0039 case QAVAudioFormat::UInt8: 0040 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0041 out.setSampleSize(8); 0042 out.setSampleType(QAudioFormat::UnSignedInt); 0043 #else 0044 out.setSampleFormat(QAudioFormat::UInt8); 0045 #endif 0046 break; 0047 case QAVAudioFormat::Int16: 0048 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0049 out.setSampleSize(16); 0050 out.setSampleType(QAudioFormat::SignedInt); 0051 #else 0052 out.setSampleFormat(QAudioFormat::Int16); 0053 #endif 0054 break; 0055 case QAVAudioFormat::Int32: 0056 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0057 out.setSampleSize(32); 0058 out.setSampleType(QAudioFormat::SignedInt); 0059 #else 0060 out.setSampleFormat(QAudioFormat::Int32); 0061 #endif 0062 break; 0063 case QAVAudioFormat::Float: 0064 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0065 out.setSampleSize(32); 0066 out.setSampleType(QAudioFormat::Float); 0067 #else 0068 out.setSampleFormat(QAudioFormat::Float); 0069 #endif 0070 break; 0071 default: 0072 qWarning() << "Could not negotiate output format:" << from.sampleFormat(); 0073 return {}; 0074 } 0075 0076 return out; 0077 } 0078 0079 class QAVAudioOutputPrivate : public QIODevice 0080 { 0081 public: 0082 QAVAudioOutputPrivate() 0083 { 0084 open(QIODevice::ReadOnly); 0085 threadPool.setMaxThreadCount(1); 0086 } 0087 0088 QFuture<void> audioPlayFuture; 0089 0090 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0091 using AudioOutput = QAudioOutput; 0092 using AudioDevice = QAudioDeviceInfo; 0093 #else 0094 using AudioOutput = QAudioSink; 0095 using AudioDevice = QAudioDevice; 0096 #endif 0097 AudioOutput *audioOutput = nullptr; 0098 qreal volume = 1.0; 0099 int bufferSize = 0; 0100 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) 0101 QAudioFormat::ChannelConfig channelConfig = QAudioFormat::ChannelConfigUnknown; 0102 #endif 0103 0104 QList<QAVAudioFrame> frames; 0105 qint64 offset = 0; 0106 bool quit = 0; 0107 AudioDevice defaultAudioDevice; 0108 mutable QMutex mutex; 0109 QWaitCondition cond; 0110 QThreadPool threadPool; 0111 0112 qint64 readData(char *data, qint64 len) override 0113 { 0114 if (!len) 0115 return 0; 0116 0117 QMutexLocker locker(&mutex); 0118 qint64 bytesWritten = 0; 0119 while (len && !quit) { 0120 if (frames.isEmpty()) { 0121 // Wait for more frames 0122 if (bytesWritten == 0) 0123 cond.wait(&mutex); 0124 if (frames.isEmpty()) 0125 break; 0126 } 0127 0128 auto frame = frames.front(); 0129 auto sampleData = frame.data(); 0130 const int toWrite = qMin(sampleData.size() - offset, len); 0131 memcpy(data, sampleData.constData() + offset, toWrite); 0132 bytesWritten += toWrite; 0133 data += toWrite; 0134 len -= toWrite; 0135 offset += toWrite; 0136 0137 if (offset >= sampleData.size()) { 0138 offset = 0; 0139 frames.removeFirst(); 0140 } 0141 } 0142 0143 return bytesWritten; 0144 } 0145 0146 qint64 writeData(const char *, qint64) override { return 0; } 0147 qint64 size() const override { return 0; } 0148 qint64 bytesAvailable() const override { return std::numeric_limits<qint64>::max(); } 0149 bool isSequential() const override { return true; } 0150 bool atEnd() const override { return false; } 0151 0152 void tryInit(const QAudioFormat &fmt, int bsize, qreal v) 0153 { 0154 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0155 auto audioDevice = QAudioDeviceInfo::defaultOutputDevice(); 0156 #else 0157 auto audioDevice = QMediaDevices::defaultAudioOutput(); 0158 #endif 0159 0160 if (!audioOutput 0161 || (fmt.isValid() && audioOutput->format() != fmt) 0162 || audioOutput->state() == QAudio::StoppedState 0163 || defaultAudioDevice != audioDevice) 0164 { 0165 if (audioOutput) { 0166 audioOutput->stop(); 0167 audioOutput->deleteLater(); 0168 } 0169 0170 audioOutput = new AudioOutput(audioDevice, fmt); 0171 defaultAudioDevice = audioDevice; 0172 0173 QObject::connect(audioOutput, &AudioOutput::stateChanged, audioOutput, 0174 [&](QAudio::State state) { 0175 switch (state) { 0176 case QAudio::StoppedState: 0177 if (audioOutput->error() != QAudio::NoError) 0178 qWarning() << "QAudioOutput stopped:" << audioOutput->error(); 0179 break; 0180 default: 0181 break; 0182 } 0183 }); 0184 0185 if (bsize > 0) 0186 audioOutput->setBufferSize(bsize); 0187 audioOutput->setVolume(v); 0188 audioOutput->start(this); 0189 } 0190 } 0191 0192 void doPlayAudio() 0193 { 0194 while (!quit) { 0195 QMutexLocker locker(&mutex); 0196 cond.wait(&mutex); 0197 auto fmt = !frames.isEmpty() ? format(frames.first().format()) : QAudioFormat(); 0198 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) 0199 fmt.setChannelConfig(channelConfig); 0200 #endif 0201 auto v = volume; 0202 auto bsize = bufferSize; 0203 locker.unlock(); 0204 if (fmt.isValid()) 0205 tryInit(fmt, bsize, v); 0206 if (audioOutput) 0207 audioOutput->setVolume(v); 0208 QCoreApplication::processEvents(); 0209 } 0210 if (audioOutput) { 0211 audioOutput->stop(); 0212 audioOutput->deleteLater(); 0213 } 0214 audioOutput = nullptr; 0215 } 0216 0217 void startThreadIfNeeded() 0218 { 0219 if (!audioPlayFuture.isRunning()) { 0220 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0221 audioPlayFuture = QtConcurrent::run(&threadPool, this, &QAVAudioOutputPrivate::doPlayAudio); 0222 #else 0223 audioPlayFuture = QtConcurrent::run(&threadPool, &QAVAudioOutputPrivate::doPlayAudio, this); 0224 #endif 0225 } 0226 } 0227 }; 0228 0229 QAVAudioOutput::QAVAudioOutput(QObject *parent) 0230 : QObject(parent) 0231 , d_ptr(new QAVAudioOutputPrivate) 0232 { 0233 } 0234 0235 QAVAudioOutput::~QAVAudioOutput() 0236 { 0237 Q_D(QAVAudioOutput); 0238 d->quit = true; 0239 d->cond.wakeAll(); 0240 d->audioPlayFuture.waitForFinished(); 0241 } 0242 0243 void QAVAudioOutput::setVolume(qreal v) 0244 { 0245 Q_D(QAVAudioOutput); 0246 QMutexLocker locker(&d->mutex); 0247 d->volume = v; 0248 d->cond.wakeAll(); 0249 } 0250 0251 qreal QAVAudioOutput::volume() const 0252 { 0253 Q_D(const QAVAudioOutput); 0254 QMutexLocker locker(&d->mutex); 0255 return d->volume; 0256 } 0257 0258 void QAVAudioOutput::setBufferSize(int bytes) 0259 { 0260 Q_D(QAVAudioOutput); 0261 QMutexLocker locker(&d->mutex); 0262 d->bufferSize = bytes; 0263 if (d->bufferSize > 0 && d->audioOutput) 0264 d->audioOutput->setBufferSize(d->bufferSize); 0265 } 0266 0267 int QAVAudioOutput::bufferSize() const 0268 { 0269 Q_D(const QAVAudioOutput); 0270 QMutexLocker locker(&d->mutex); 0271 return d->bufferSize; 0272 } 0273 0274 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) 0275 void QAVAudioOutput::setChannelConfig(QAudioFormat::ChannelConfig config) 0276 { 0277 Q_D(QAVAudioOutput); 0278 QMutexLocker locker(&d->mutex); 0279 d->channelConfig = config; 0280 } 0281 0282 QAudioFormat::ChannelConfig QAVAudioOutput::channelConfig() const 0283 { 0284 Q_D(const QAVAudioOutput); 0285 QMutexLocker locker(&d->mutex); 0286 return d->channelConfig; 0287 } 0288 0289 #endif 0290 0291 bool QAVAudioOutput::play(const QAVAudioFrame &frame) 0292 { 0293 Q_D(QAVAudioOutput); 0294 if (d->quit || !frame) 0295 return false; 0296 0297 QMutexLocker locker(&d->mutex); 0298 d->startThreadIfNeeded(); 0299 d->frames.push_back(frame); 0300 d->cond.wakeAll(); 0301 0302 return true; 0303 } 0304 0305 QT_END_NAMESPACE