292 lines
10 KiB
C++
292 lines
10 KiB
C++
/******************************************************************************
|
|
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)
|