/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * 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 #include "Logger.h" #if (SELECT_AUDIO_BACKEND) #include "../rm_settings.h" #endif #if (FILE_LOADING_TIMELAPSE) #include 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;jdeviceCount;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> globalSounddeviceList() { QList> 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(name,i)); } } if(!AudioOutputPortAudio::initialized) { Pa_Terminate(); } return ret; } #endif // #if (SELECT_AUDIO_BACKEND) } //namespace FAV #endif // #if !(REMOVE_AUDIO_PORTAUDIO)