first commit
This commit is contained in:
441
project/fm_viewer/fav/AudioOutputOpenAL.cpp
Normal file
441
project/fm_viewer/fav/AudioOutputOpenAL.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
/******************************************************************************
|
||||
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_OPENAL 1
|
||||
#if !(REMOVE_AUDIO_OPENAL)
|
||||
|
||||
#include "AudioOutputBackend.h"
|
||||
#include "mkid.h"
|
||||
#include "factory.h"
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QWaitCondition>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
#if QTAV_HAVE(CAPI)
|
||||
#define OPENAL_CAPI_NS // CAPI_LINK_OPENAL will override it
|
||||
#include "capi/openal_api.h"
|
||||
#else
|
||||
#if defined(HEADER_OPENAL_PREFIX)
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
#endif //QTAV_HAVE(CAPI)
|
||||
#include "Logger.h"
|
||||
|
||||
#define UNQUEUE_QUICK 0
|
||||
|
||||
namespace FAV {
|
||||
|
||||
static const char kName[] = "OpenAL";
|
||||
class AudioOutputOpenAL Q_DECL_FINAL: public AudioOutputBackend
|
||||
{
|
||||
public:
|
||||
AudioOutputOpenAL(QObject* parent = 0);
|
||||
QString name() const Q_DECL_FINAL { return QLatin1String(kName);}
|
||||
QString status() const Q_DECL_OVERRIDE {return "";};
|
||||
QString deviceName() const;
|
||||
bool open() Q_DECL_FINAL;
|
||||
bool close() Q_DECL_FINAL;
|
||||
bool isSupported(const AudioFormat& format) const Q_DECL_FINAL;
|
||||
bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_FINAL;
|
||||
bool isSupported(AudioFormat::ChannelLayout channelLayout) const Q_DECL_FINAL;
|
||||
protected:
|
||||
BufferControl bufferControl() const Q_DECL_FINAL;
|
||||
bool write(const QByteArray& data) Q_DECL_FINAL;
|
||||
bool play() Q_DECL_FINAL;
|
||||
int getPlayedCount() Q_DECL_FINAL;
|
||||
bool setVolume(qreal value) Q_DECL_FINAL;
|
||||
qreal getVolume() const Q_DECL_FINAL;
|
||||
int getQueued();
|
||||
|
||||
bool openDevice() {
|
||||
if (context)
|
||||
return true;
|
||||
const ALCchar *default_device = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
|
||||
qDebug("OpenAL opening default device: %s", default_device);
|
||||
device = alcOpenDevice(NULL); //parameter: NULL or default_device
|
||||
if (!device) {
|
||||
qWarning("OpenAL failed to open sound device: %s", alcGetString(0, alcGetError(0)));
|
||||
return false;
|
||||
}
|
||||
qDebug("AudioOutputOpenAL creating context...");
|
||||
context = alcCreateContext(device, NULL);
|
||||
alcMakeContextCurrent(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
ALCdevice *device;
|
||||
ALCcontext *context;
|
||||
ALenum format_al;
|
||||
QVector<ALuint> buffer;
|
||||
ALuint source;
|
||||
ALint state;
|
||||
QMutex mutex;
|
||||
QWaitCondition cond;
|
||||
|
||||
// used for 1 context per instance. lock when makeCurrent
|
||||
static QMutex global_mutex;
|
||||
};
|
||||
|
||||
typedef AudioOutputOpenAL AudioOutputBackendOpenAL;
|
||||
static const AudioOutputBackendId AudioOutputBackendId_OpenAL = mkid::id32base36_6<'O', 'p', 'e', 'n', 'A', 'L'>::value;
|
||||
FACTORY_REGISTER(AudioOutputBackend, OpenAL, kName)
|
||||
|
||||
#define AL_ENSURE(expr, ...) \
|
||||
do { \
|
||||
expr; \
|
||||
const ALenum err = alGetError(); \
|
||||
if (err != AL_NO_ERROR) { \
|
||||
qWarning("AudioOutputOpenAL Error>>> " #expr " (%d) : %s", err, alGetString(err)); \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define SCOPE_LOCK_CONTEXT() \
|
||||
QMutexLocker ctx_lock(&global_mutex); \
|
||||
Q_UNUSED(ctx_lock); \
|
||||
if (context) \
|
||||
alcMakeContextCurrent(context)
|
||||
|
||||
static ALenum audioFormatToAL(const AudioFormat& fmt)
|
||||
{
|
||||
if (fmt.isPlanar())
|
||||
return 0;
|
||||
typedef union {
|
||||
const char* ext;
|
||||
ALenum fmt;
|
||||
} al_fmt_t;
|
||||
ALenum format = 0;
|
||||
// al functions need a context
|
||||
ALCcontext *ctx = alcGetCurrentContext(); //a context is required for al functions!
|
||||
const int c = fmt.channels();
|
||||
const AudioFormat::SampleFormat spfmt = fmt.sampleFormat(); //TODO: planar formats are fine too
|
||||
if (AudioFormat::SampleFormat_Unsigned8 == spfmt) {
|
||||
static const al_fmt_t u8fmt[] = {
|
||||
{(const char*)AL_FORMAT_MONO8},
|
||||
{(const char*)AL_FORMAT_STEREO8},
|
||||
{(const char*)0},
|
||||
{"AL_FORMAT_QUAD8"},
|
||||
{"AL_FORMAT_REAR8"},
|
||||
{"AL_FORMAT_51CHN8"},
|
||||
{"AL_FORMAT_61CHN8"},
|
||||
{"AL_FORMAT_71CHN8"}
|
||||
};
|
||||
if (c < 3) {
|
||||
format = u8fmt[c-1].fmt;
|
||||
} else if (c > 3 && c <= 8 && ctx) {
|
||||
if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
format = alGetEnumValue(u8fmt[c-1].ext);
|
||||
}
|
||||
} else if (AudioFormat::SampleFormat_Signed16 == spfmt) {
|
||||
static const al_fmt_t s16fmt[] = {
|
||||
{(const char*)AL_FORMAT_MONO16},
|
||||
{(const char*)AL_FORMAT_STEREO16},
|
||||
{(const char*)0},
|
||||
{"AL_FORMAT_QUAD16"},
|
||||
{"AL_FORMAT_REAR16"},
|
||||
{"AL_FORMAT_51CHN16"},
|
||||
{"AL_FORMAT_61CHN16"},
|
||||
{"AL_FORMAT_71CHN16"}
|
||||
};
|
||||
if (c < 3) {
|
||||
format = s16fmt[c-1].fmt;
|
||||
} else if (c > 3 && c <= 8 && ctx) {
|
||||
if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
|
||||
format = alGetEnumValue(s16fmt[c-1].ext);
|
||||
}
|
||||
} else if (ctx) {
|
||||
if (AudioFormat::SampleFormat_Float == spfmt) {
|
||||
static const al_fmt_t f32fmt[] = {
|
||||
{"AL_FORMAT_MONO_FLOAT32"},
|
||||
{"AL_FORMAT_STEREO_FLOAT32"},
|
||||
{0},
|
||||
// AL_EXT_MCFORMATS
|
||||
{"AL_FORMAT_QUAD32"},
|
||||
{"AL_FORMAT_REAR32"},
|
||||
{"AL_FORMAT_51CHN32"},
|
||||
{"AL_FORMAT_61CHN32"},
|
||||
{"AL_FORMAT_71CHN32"}
|
||||
};
|
||||
if (c <=8 && f32fmt[c-1].ext) {
|
||||
format = alGetEnumValue(f32fmt[c-1].ext);
|
||||
}
|
||||
} else if (AudioFormat::SampleFormat_Double == spfmt) {
|
||||
if (c < 3) {
|
||||
if (alIsExtensionPresent("AL_EXT_double")) {
|
||||
static const al_fmt_t d64fmt[] = {
|
||||
{"AL_FORMAT_MONO_DOUBLE_EXT"},
|
||||
{"AL_FORMAT_STEREO_DOUBLE_EXT"}
|
||||
};
|
||||
format = alGetEnumValue(d64fmt[c-1].ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ALCenum err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
if (ctx)
|
||||
qWarning("OpenAL audioFormatToAL error: %s", alGetString(err));
|
||||
else
|
||||
qWarning("OpenAL audioFormatToAL error (null context): %#x", err);
|
||||
}
|
||||
if (format == 0) {
|
||||
qWarning("AudioOutputOpenAL Error: No OpenAL format available for audio data format %s %s."
|
||||
, qPrintable(fmt.sampleFormatName())
|
||||
, qPrintable(fmt.channelLayoutName()));
|
||||
}
|
||||
qDebug("OpenAL audio format: %#x ch:%d, sample format: %s", format, fmt.channels(), qPrintable(fmt.sampleFormatName()));
|
||||
return format;
|
||||
}
|
||||
|
||||
QMutex AudioOutputOpenAL::global_mutex;
|
||||
|
||||
AudioOutputOpenAL::AudioOutputOpenAL(QObject *parent)
|
||||
: AudioOutputBackend(AudioOutput::SetVolume, parent)
|
||||
, device(0)
|
||||
, context(0)
|
||||
, format_al(AL_FORMAT_STEREO16)
|
||||
, state(0)
|
||||
{
|
||||
#if QTAV_HAVE(CAPI)
|
||||
#ifndef CAPI_LINK_OPENAL
|
||||
if (!openal::capi::loaded()) {
|
||||
available = false;
|
||||
return;
|
||||
}
|
||||
#endif //CAPI_LINK_OPENAL
|
||||
#endif
|
||||
//setDeviceFeatures(AudioOutput::SetVolume);
|
||||
// ensure we have a context to check format support
|
||||
// TODO: AudioOutput::getDevices() => ao.setDevice() => ao.open
|
||||
QVector<QByteArray> _devices;
|
||||
const char *p = NULL;
|
||||
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT")) {
|
||||
// ALC_ALL_DEVICES_SPECIFIER maybe not defined
|
||||
p = alcGetString(NULL, alcGetEnumValue(NULL, "ALC_ALL_DEVICES_SPECIFIER"));
|
||||
} else {
|
||||
p = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
||||
}
|
||||
while (p && *p) {
|
||||
_devices.push_back(p);
|
||||
p += _devices.last().size() + 1;
|
||||
}
|
||||
qDebug() << _devices;
|
||||
available = openDevice(); //ensure isSupported(AudioFormat) works correctly
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::open()
|
||||
{
|
||||
if (!openDevice())
|
||||
return false;
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
// alGetString: alsoft needs a context. apple does not
|
||||
qDebug("OpenAL %s vendor: %s; renderer: %s", alGetString(AL_VERSION), alGetString(AL_VENDOR), alGetString(AL_RENDERER));
|
||||
//alcProcessContext(ctx); //used when dealing witg multiple contexts
|
||||
ALCenum err = alcGetError(device);
|
||||
if (err != ALC_NO_ERROR) {
|
||||
qWarning("AudioOutputOpenAL Error: %s", alcGetString(device, err));
|
||||
return false;
|
||||
}
|
||||
qDebug("device: %p, context: %p", device, context);
|
||||
//init params. move to another func?
|
||||
format_al = audioFormatToAL(format);
|
||||
|
||||
buffer.resize(buffer_count);
|
||||
alGenBuffers(buffer.size(), buffer.data());
|
||||
err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
qWarning("Failed to generate OpenAL buffers: %s", alGetString(err));
|
||||
goto fail;
|
||||
}
|
||||
alGenSources(1, &source);
|
||||
err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
qWarning("Failed to generate OpenAL source: %s", alGetString(err));
|
||||
alDeleteBuffers(buffer.size(), buffer.constData());
|
||||
goto fail;
|
||||
}
|
||||
alSourcei(source, AL_LOOPING, AL_FALSE);
|
||||
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
alSourcei(source, AL_ROLLOFF_FACTOR, 0);
|
||||
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
|
||||
alSource3f(source, AL_VELOCITY, 0.0, 0.0, 0.0);
|
||||
alListener3f(AL_POSITION, 0.0, 0.0, 0.0);
|
||||
state = 0;
|
||||
qDebug("AudioOutputOpenAL open ok...");
|
||||
}
|
||||
return true;
|
||||
fail:
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(context);
|
||||
alcCloseDevice(device);
|
||||
context = 0;
|
||||
device = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::close()
|
||||
{
|
||||
state = 0;
|
||||
if (!context)
|
||||
return true;
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
alSourceStop(source);
|
||||
do {
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
} while (alGetError() == AL_NO_ERROR && state == AL_PLAYING);
|
||||
ALint processed = 0; //android need this!! otherwise the value may be undefined
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
|
||||
ALuint buf;
|
||||
while (processed-- > 0) { alSourceUnqueueBuffers(source, 1, &buf); }
|
||||
alDeleteSources(1, &source);
|
||||
alDeleteBuffers(buffer.size(), buffer.constData());
|
||||
|
||||
alcMakeContextCurrent(NULL);
|
||||
qDebug("alcDestroyContext(%p)", context);
|
||||
alcDestroyContext(context);
|
||||
ALCenum err = alcGetError(device);
|
||||
if (err != ALC_NO_ERROR) { //ALC_INVALID_CONTEXT
|
||||
qWarning("AudioOutputOpenAL Failed to destroy context: %s", alcGetString(device, err));
|
||||
return false;
|
||||
}
|
||||
context = 0;
|
||||
if (device) {
|
||||
qDebug("alcCloseDevice(%p)", device);
|
||||
alcCloseDevice(device);
|
||||
// ALC_INVALID_DEVICE now
|
||||
device = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::isSupported(const AudioFormat& format) const
|
||||
{
|
||||
//if (!context)
|
||||
// openDevice(); //not const
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
return !!audioFormatToAL(format);
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::isSupported(AudioFormat::SampleFormat sampleFormat) const
|
||||
{
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Unsigned8 || sampleFormat == AudioFormat::SampleFormat_Signed16)
|
||||
return true;
|
||||
if (IsPlanar(sampleFormat))
|
||||
return false;
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Float)
|
||||
return alIsExtensionPresent("AL_EXT_float32");
|
||||
if (sampleFormat == AudioFormat::SampleFormat_Double)
|
||||
return alIsExtensionPresent("AL_EXT_double");
|
||||
// because preferredChannelLayout() is stereo while s32 only supports >3 channels, so always false
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::isSupported(AudioFormat::ChannelLayout channelLayout) const // FIXME: check
|
||||
{
|
||||
return channelLayout == AudioFormat::ChannelLayout_Mono || channelLayout == AudioFormat::ChannelLayout_Stereo;
|
||||
}
|
||||
|
||||
QString AudioOutputOpenAL::deviceName() const
|
||||
{
|
||||
if (!device)
|
||||
return QString();
|
||||
const ALCchar *name = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
||||
return QString::fromUtf8(name);
|
||||
}
|
||||
|
||||
AudioOutputBackend::BufferControl AudioOutputOpenAL::bufferControl() const
|
||||
{
|
||||
return PlayedCount; //TODO: AL_BYTE_OFFSET
|
||||
}
|
||||
|
||||
// http://kcat.strangesoft.net/openal-tutorial.html
|
||||
bool AudioOutputOpenAL::write(const QByteArray& data)
|
||||
{
|
||||
if (data.isEmpty())
|
||||
return false;
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALuint buf = 0;
|
||||
if (state <= 0) { //state used for filling initial data
|
||||
buf = buffer[(-state)%buffer_count];
|
||||
--state;
|
||||
} else {
|
||||
AL_ENSURE(alSourceUnqueueBuffers(source, 1, &buf), false);
|
||||
}
|
||||
AL_ENSURE(alBufferData(buf, format_al, data.constData(), data.size(), format.sampleRate()), false);
|
||||
AL_ENSURE(alSourceQueueBuffers(source, 1, &buf), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::play()
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) {
|
||||
qDebug("AudioOutputOpenAL: !AL_PLAYING alSourcePlay");
|
||||
alSourcePlay(source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int AudioOutputOpenAL::getPlayedCount()
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALint processed = 0;
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
|
||||
return processed;
|
||||
}
|
||||
|
||||
bool AudioOutputOpenAL::setVolume(qreal value)
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
AL_ENSURE(alListenerf(AL_GAIN, value), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
qreal AudioOutputOpenAL::getVolume() const
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALfloat v = 1.0;
|
||||
alGetListenerf(AL_GAIN, &v);
|
||||
ALenum err = alGetError();
|
||||
if (err != AL_NO_ERROR) {
|
||||
qWarning("AudioOutputOpenAL Error>>> getVolume (%d) : %s", err, alGetString(err));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
int AudioOutputOpenAL::getQueued()
|
||||
{
|
||||
SCOPE_LOCK_CONTEXT();
|
||||
ALint queued = 0;
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
return queued;
|
||||
}
|
||||
|
||||
} //namespace FAV
|
||||
|
||||
#endif // #if !(REMOVE_AUDIO_OPENAL)
|
||||
Reference in New Issue
Block a user