Files
fmviewer3/project/fm_viewer/fav/AudioOutputOpenAL.cpp
2026-02-21 17:11:31 +09:00

442 lines
14 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_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)