first commit

This commit is contained in:
2026-02-21 17:11:31 +09:00
commit 18b4338361
4001 changed files with 365464 additions and 0 deletions

View File

@@ -0,0 +1,291 @@
/******************************************************************************
QtAV: Multimedia framework based on Qt and FFmpeg
Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
* This file is part of QtAV
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
******************************************************************************/
#define REMOVE_AUDIO_PORTAUDIO 0
#if !(REMOVE_AUDIO_PORTAUDIO)
#include "AudioOutputBackend.h"
#include "mkid.h"
#include "factory.h"
#include <portaudio.h>
#include "Logger.h"
#if (SELECT_AUDIO_BACKEND)
#include "../rm_settings.h"
#endif
#if (FILE_LOADING_TIMELAPSE)
#include <QElapsedTimer>
QElapsedTimer gPALoadingTimer;
#endif
namespace FAV {
static const char kName[] = "PortAudio";
class AudioOutputPortAudio Q_DECL_FINAL: public AudioOutputBackend
{
public:
AudioOutputPortAudio(QObject *parent = 0);
~AudioOutputPortAudio();
QString name() const Q_DECL_FINAL { return QString::fromLatin1(kName);}
QString status() const Q_DECL_OVERRIDE {return _status;};
bool open() Q_DECL_FINAL;
bool close() Q_DECL_FINAL;
virtual BufferControl bufferControl() const Q_DECL_FINAL;
virtual bool write(const QByteArray& data) Q_DECL_FINAL;
virtual bool play() Q_DECL_FINAL { return true;}
static bool initialized;
private:
PaStreamParameters *outputParameters;
PaStream *stream;
double outputLatency;
QString _status;
};
typedef AudioOutputPortAudio AudioOutputBackendPortAudio;
static const AudioOutputBackendId AudioOutputBackendId_PortAudio = mkid::id32base36_5<'P', 'o', 'r', 't', 'A'>::value;
FACTORY_REGISTER(AudioOutputBackend, PortAudio, kName)
bool AudioOutputPortAudio::initialized = false;
AudioOutputPortAudio::AudioOutputPortAudio(QObject *parent)
: AudioOutputBackend(AudioOutput::NoFeature, parent)
// , initialized(false)
, outputParameters(new PaStreamParameters)
, stream(0)
{
//qInfo() << "PORTAUDIO VERSION:" << Pa_GetVersionText() << __FUNCTION__;
_status = "\n";
PaError err = paNoError;
if ((err = Pa_Initialize()) != paNoError) {
qWarning("Error when init portaudio: %s", Pa_GetErrorText(err));
_status += " PORTAUDIO INIT ERROR\n";
return;
}
initialized = true;
int numDevices = Pa_GetDeviceCount();
_status += QString().sprintf("\n PORTAUDIO NUM DEVICE:%d\n",numDevices);
//qInfo() << QString().sprintf("\n PORTAUDIO NUM DEVICE:%d\n",numDevices);
#if (SELECT_AUDIO_BACKEND)
int selected = -1;
QString selectedName = RMSettings::instance()->AudioDevice();
//qInfo() << "selectedName:" << selectedName << __FUNCTION__;
#endif
for (int i = 0 ; i < numDevices ; ++i) {
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (deviceInfo && deviceInfo->maxOutputChannels > 0) {
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
QString name = QString::fromUtf8(hostApiInfo->name) + QStringLiteral(": ") + QString::fromUtf8(deviceInfo->name); // QString::fromLocal8Bit(deviceInfo->name)
//_status += (QString().sprintf(" PORTAUDIO DEVICE %d(%d):",i,hostApiInfo->defaultOutputDevice) + name + "\n");
#if (SELECT_AUDIO_BACKEND)
if(name.startsWith("Windows ")) {
name = name.remove(0,QString("Windows ").length());
}
if(!selectedName.isEmpty() && selectedName == name) {
selected = i;
}
#endif
//qInfo() << (QString().sprintf(" PORTAUDIO DEVICE %d(%d):",i,hostApiInfo->defaultOutputDevice) + name) << "MAX CHANNEL:" << deviceInfo->maxOutputChannels << __FUNCTION__;
// if(i == 3) {
// hostApiInfo->defaultOutputDevice = 4;
// }
// for(int j=0;j<hostApiInfo->deviceCount;j++) {
// }
//qDebug("audio device %d: %s", i, name.toUtf8().constData());
//qDebug("max in/out channels: %d/%d", deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels);
}
}
memset(outputParameters, 0, sizeof(PaStreamParameters));
outputParameters->device = Pa_GetDefaultOutputDevice();
#if (SELECT_AUDIO_BACKEND)
if(selected >= 0) {
outputParameters->device = selected;
}
#endif // SELECT_AUDIO_BACKEND
//qInfo() << "SELECTED:" << outputParameters->device << __FUNCTION__;
if (outputParameters->device == paNoDevice) {
qWarning("PortAudio get device error!");
return;
}
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(outputParameters->device);
_status += (" PORTAUDIO SELECTED:" + QString::fromLocal8Bit(deviceInfo->name) + "\n");
qDebug("DEFAULT max in/out channels: %d/%d", deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels);
qDebug("audio device: %s", QString::fromUtf8(Pa_GetDeviceInfo(outputParameters->device)->name).toUtf8().constData());
outputParameters->hostApiSpecificStreamInfo = NULL;
outputParameters->suggestedLatency = Pa_GetDeviceInfo(outputParameters->device)->defaultHighOutputLatency;
}
AudioOutputPortAudio::~AudioOutputPortAudio()
{
if (outputParameters) {
delete outputParameters;
outputParameters = 0;
}
//Do NOT call this if init failed. See document
// WAAAAAAAA!!!!!
if (initialized) {
Pa_Terminate();
initialized = false;
}
}
AudioOutputBackend::BufferControl AudioOutputPortAudio::bufferControl() const
{
return Blocking;
}
bool AudioOutputPortAudio::write(const QByteArray& data)
{
if (Pa_IsStreamStopped(stream))
Pa_StartStream(stream);
PaError err = Pa_WriteStream(stream, data.constData(), data.size()/format.channels()/format.bytesPerSample());
if (err == paUnanticipatedHostError) {
//qInfo() << Pa_GetErrorText(err) << __FUNCTION__;
//qWarning("Write portaudio stream error: %s", Pa_GetErrorText(err));
return false;
}
return true;
}
//TODO: what about planar, int8, int24 etc that FFmpeg or Pa not support?
static int toPaSampleFormat(AudioFormat::SampleFormat format)
{
switch (format) {
case AudioFormat::SampleFormat_Unsigned8:
return paUInt8;
case AudioFormat::SampleFormat_Signed16:
return paInt16;
case AudioFormat::SampleFormat_Signed32:
return paInt32;
case AudioFormat::SampleFormat_Float:
return paFloat32;
default:
return paCustomFormat;
}
}
//TODO: call open after audio format changed?
bool AudioOutputPortAudio::open()
{
outputParameters->sampleFormat = toPaSampleFormat(format.sampleFormat());
outputParameters->channelCount = format.channels();
//qInfo() << "PaSampleFormat:" << outputParameters->sampleFormat << " CHANNEL:" << outputParameters->channelCount << "SR:" << format.sampleRate();
#if (FILE_LOADING_TIMELAPSE)
gPALoadingTimer.start();
#endif
// buffer = 0
PaError err = Pa_OpenStream(&stream, NULL, outputParameters, format.sampleRate(), 0 , paNoFlag, NULL, NULL);
if(err == -9997) {
err = Pa_OpenStream(&stream, NULL, outputParameters, 48000, 0, paNoFlag, NULL, NULL);
}
if (err != paNoError) {
qInfo() << "Pa_OpenStream ERROR:" << err << Pa_GetErrorText(err) << __FUNCTION__;
qWarning("Open portaudio stream error: %s", Pa_GetErrorText(err));
return false;
}
#if (FILE_LOADING_TIMELAPSE)
//qInfo() << "PA ELAPSED:" << gPALoadingTimer.elapsed();
#endif
outputLatency = Pa_GetStreamInfo(stream)->outputLatency;
#if (FILE_LOADING_TIMELAPSE)
//qInfo() << "PA ELAPSED:" << gPALoadingTimer.elapsed();
#endif
//qInfo() << "OPEN STREAM OK SR:" << format.sampleRate() << __FUNCTION__;
return true;
}
bool AudioOutputPortAudio::close()
{
if (!stream) {
return true;
}
PaError err = Pa_StopStream(stream); //may be already stopped: paStreamIsStopped
if (err != paNoError) {
qWarning("Stop portaudio stream error: %s", Pa_GetErrorText(err));
//return err == paStreamIsStopped;
}
err = Pa_CloseStream(stream);
if (err != paNoError) {
qWarning("Close portaudio stream error: %s", Pa_GetErrorText(err));
return false;
}
stream = NULL;
// DO NOT TERMINATE!!!!
// if (initialized)
// Pa_Terminate(); //Do NOT call this if init failed. See document
//qInfo() << "AudioOutputPortAudio::close() done";
return true;
}
#if (SELECT_AUDIO_BACKEND)
QList<QPair<QString,int>> globalSounddeviceList()
{
QList<QPair<QString,int>> ret;
if(!AudioOutputPortAudio::initialized) {
//qInfo() << "INIT PORT AUDIO" << __FUNCTION__;
PaError err = paNoError;
if ((err = Pa_Initialize()) != paNoError) {
qWarning("Error when init portaudio: %s", Pa_GetErrorText(err));
return ret;
}
}
int numDevices = Pa_GetDeviceCount();
for (int i = 0 ; i < numDevices ; ++i) {
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (deviceInfo && deviceInfo->maxOutputChannels > 0) {
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
QString name = QString::fromUtf8(hostApiInfo->name) + QStringLiteral(": ") + QString::fromUtf8(deviceInfo->name);
if(name.startsWith("Windows ")) {
name = name.remove(0,QString("Windows ").length());
}
ret.append(QPair<QString,int>(name,i));
}
}
if(!AudioOutputPortAudio::initialized) {
Pa_Terminate();
}
return ret;
}
#endif // #if (SELECT_AUDIO_BACKEND)
} //namespace FAV
#endif // #if !(REMOVE_AUDIO_PORTAUDIO)