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