audio: add dsound. not tested

This commit is contained in:
wang-bin
2014-07-30 19:53:36 +08:00
parent be6601dc11
commit 3fdb5bbebf
6 changed files with 776 additions and 343 deletions
+418
View File
@@ -0,0 +1,418 @@
/******************************************************************************
AudioOutputDSound.cpp: description
Copyright (C) 2012-2014 Wang Bin <wbsecg1@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "QtAV/AudioOutput.h"
#include "QtAV/private/AudioOutput_p.h"
#include "prepost.h"
#include <QtCore/QLibrary>
#include <QtCore/QVector>
#include <windows.h>
#define DIRECTSOUND_VERSION 0x0600
#include <dsound.h>
#include "QtAV/private/AVCompat.h"
namespace QtAV {
class AudioOutputDSoundPrivate;
class AudioOutputDSound : public AudioOutput
{
DPTR_DECLARE_PRIVATE(AudioOutputDSound)
public:
AudioOutputDSound();
~AudioOutputDSound();
//AudioOutputId id() const
virtual bool open();
virtual bool close();
virtual bool isSupported(const AudioFormat& format) const;
virtual void waitForNextBuffer();
protected:
virtual bool write();
};
extern AudioOutputId AudioOutputId_DSound;
FACTORY_REGISTER_ID_AUTO(AudioOutput, DSound, "DirectSound")
void RegisterAudioOutputDSound_Man()
{
FACTORY_REGISTER_ID_MAN(AudioOutput, DSound, "DirectSound")
}
template <class T> void SafeRelease(T **ppT) {
if (*ppT) {
(*ppT)->Release();
*ppT = NULL;
}
}
// use the definitions from the win32 api headers when they define these
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
/* GUID SubFormat IDs */
/* We need both b/c const variables are not compile-time constants in C, giving
* us an error if we use the const GUID in an enum */
#ifndef _KSDATAFORMAT_SUBTYPE_PCM_
#define _KSDATAFORMAT_SUBTYPE_PCM_ {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}
static const GUID VLC_KSDATAFORMAT_SUBTYPE_PCM = {0xE923AABF, 0xCB58, 0x4471, {0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62}};
#define KSDATAFORMAT_SUBTYPE_PCM VLC_KSDATAFORMAT_SUBTYPE_PCM
#endif
#ifndef _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_
#define _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_ {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}
static const GUID VLC_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
#define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT VLC_KSDATAFORMAT_SUBTYPE_PCM
#endif
#ifndef _KSDATAFORMAT_SUBTYPE_UNKNOWN_
#define _KSDATAFORMAT_SUBTYPE_UNKNOWN_ {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
static const GUID VLC_KSDATAFORMAT_SUBTYPE_UNKNOWN = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
#define KSDATAFORMAT_SUBTYPE_UNKNOWN VLC_KSDATAFORMAT_SUBTYPE_UNKNOWN
#endif
#ifndef _WAVEFORMATEXTENSIBLE_
typedef struct {
WAVEFORMATEX Format;
union {
WORD wValidBitsPerSample; /* bits of precision */
WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */
WORD wReserved; /* If neither applies, set to zero. */
} Samples;
DWORD dwChannelMask; /* which channels are */
/* present in stream */
GUID SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
#endif
/* Microsoft speaker definitions. key/values are equal to FFmpeg's */
#define SPEAKER_FRONT_LEFT 0x1
#define SPEAKER_FRONT_RIGHT 0x2
#define SPEAKER_FRONT_CENTER 0x4
#define SPEAKER_LOW_FREQUENCY 0x8
#define SPEAKER_BACK_LEFT 0x10
#define SPEAKER_BACK_RIGHT 0x20
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
#define SPEAKER_BACK_CENTER 0x100
#define SPEAKER_SIDE_LEFT 0x200
#define SPEAKER_SIDE_RIGHT 0x400
#define SPEAKER_TOP_CENTER 0x800
#define SPEAKER_TOP_FRONT_LEFT 0x1000
#define SPEAKER_TOP_FRONT_CENTER 0x2000
#define SPEAKER_TOP_FRONT_RIGHT 0x4000
#define SPEAKER_TOP_BACK_LEFT 0x8000
#define SPEAKER_TOP_BACK_CENTER 0x10000
#define SPEAKER_TOP_BACK_RIGHT 0x20000
#define SPEAKER_RESERVED 0x80000000
static int channelMaskToMS(qint64 av) {
if (av >= (qint64)SPEAKER_RESERVED)
return 0;
return (int)av;
}
static int channelLayoutToMS(qint64 av) {
return channelMaskToMS(av);
}
static const char* dserr2str(int err) {
switch (err) {
case DS_OK: return "DS_OK";
case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
case DSERR_GENERIC: return "DSERR_GENERIC";
case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
case DSERR_NODRIVER: return "DSERR_NODRIVER";
case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
default: return "unknown";
}
}
class AudioOutputDSoundPrivate : public AudioOutputPrivate
{
public:
AudioOutputDSoundPrivate()
: AudioOutputPrivate()
, dll(NULL)
, prim_buf(NULL)
, stream_buf(NULL)
, write_offset(0)
{
}
~AudioOutputDSoundPrivate() {
}
bool loadDll();
bool unloadDll();
bool init();
void destroy() {
SafeRelease(&dsound);
SafeRelease(&prim_buf);
SafeRelease(&stream_buf);
unloadDll();
}
bool createDSoundBuffers();
HINSTANCE dll;
LPDIRECTSOUND dsound; ///direct sound object
LPDIRECTSOUNDBUFFER prim_buf; ///primary direct sound buffer
LPDIRECTSOUNDBUFFER stream_buf; ///secondary direct sound buffer (stream buffer)
int write_offset; ///offset of the write cursor in the direct sound buffer
};
AudioOutputDSound::AudioOutputDSound()
:AudioOutput(*new AudioOutputDSoundPrivate())
{
}
AudioOutputDSound::~AudioOutputDSound()
{
close();
}
bool AudioOutputDSound::open()
{
DPTR_D(AudioOutputDSound);
if (!d.init())
return false;
if (!d.createDSoundBuffers())
return false;
return true;
}
bool AudioOutputDSound::close()
{
DPTR_D(AudioOutputDSound);
d.destroy();
return true;
}
bool AudioOutputDSound::isSupported(const AudioFormat &format) const
{
Q_UNUSED(format);
return true;
}
void AudioOutputDSound::waitForNextBuffer()
{
DPTR_D(AudioOutputDSound);
if (!d.dsound)
return;
DWORD read_offset;
d.stream_buf->GetCurrentPosition(&read_offset /*play*/, NULL /*write*/);
int remain = d.write_offset - read_offset;
if (remain < 0LL) {
remain += d.bufferSizeTotal();
}
if (remain > 0) {
// don't need AudioOutputPrivate buffer queue
unsigned long duration = d.format.durationForBytes((qint64)remain)/1000LL;
QMutexLocker lock(&d.mutex);
Q_UNUSED(lock);
d.cond.wait(&d.mutex, duration);
}
d.bufferRemoved(); //TODO: virtual void noWait();
}
bool AudioOutputDSound::write() //TODO: deprecated, use write(AudioFrame or QByteArray)
{
DPTR_D(AudioOutputDSound);
LPVOID dst1, dst2;
DWORD size1, size2;
if (d.write_offset > d.bufferSizeTotal())
d.write_offset = 0;
HRESULT res = d.stream_buf->Lock(d.write_offset, d.data.size(), &dst1, &size1, &dst2, &size2, 0);
if (res == DSERR_BUFFERLOST) {
d.stream_buf->Restore();
res = d.stream_buf->Lock(d.write_offset, d.data.size(), &dst1, &size1, &dst2, &size2, 0);
}
if (res != DS_OK) {
qWarning("Can not lock secondary buffer (%s)", dserr2str(res));
return false;
}
memcpy(dst1, d.data.constData(), size1);
if (dst2)
memcpy(dst2, d.data.constData() + size1, size2);
d.write_offset += size1 + size2;
if (d.write_offset > d.bufferSizeTotal())
d.write_offset = size2;
res = d.stream_buf->Unlock(dst1, size1, dst2, size2);
if (res != DS_OK) {
qWarning("Unloack error (%s)",dserr2str(res));
//return false;
}
DWORD status;
d.stream_buf->GetStatus(&status);
if (!(status & DSBSTATUS_PLAYING)) {
d.stream_buf->Play(0, 0, DSBPLAY_LOOPING);
}
return true;
}
bool AudioOutputDSoundPrivate::loadDll()
{
dll = LoadLibrary(TEXT("dsound.dll"));
if (!dll) {
qWarning("Can not load dsound.dll");
return false;
}
return true;
}
bool AudioOutputDSoundPrivate::unloadDll()
{
if (dll)
FreeLibrary(dll);
return true;
}
bool AudioOutputDSoundPrivate::init()
{
if (!loadDll())
return false;
typedef HRESULT (WINAPI *DirectSoundCreateFunc)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
//typedef HRESULT (WINAPI *DirectSoundEnumerateFunc)(LPDSENUMCALLBACKA, LPVOID);
DirectSoundCreateFunc dsound_create = (DirectSoundCreateFunc)GetProcAddress(dll, "DirectSoundCreate");
//DirectSoundEnumerateFunc dsound_enumerate = (DirectSoundEnumerateFunc)GetProcAddress(dll, "DirectSoundEnumerateA");
if (!dsound_create) {
qWarning("Failed to resolve 'DirectSoundCreate'");
unloadDll();
return false;
}
if (FAILED(dsound_create(NULL/*dev guid*/, &dsound, NULL))){
unloadDll();
return false;
}
/* DSSCL_EXCLUSIVE: can modify the settings of the primary buffer, only the sound of this app will be hearable when it will have the focus.
*/
if (FAILED(dsound->SetCooperativeLevel(GetDesktopWindow(), DSSCL_EXCLUSIVE))) {
qWarning("Cannot set direct sound cooperative level");
SafeRelease(&dsound);
return false;
}
qDebug("DirectSound initialized.");
DSCAPS dscaps;
memset(&dscaps, 0, sizeof(DSCAPS));
dscaps.dwSize = sizeof(DSCAPS);
if (FAILED(dsound->GetCaps(&dscaps))) {
qWarning("Cannot get device capabilities.");
SafeRelease(&dsound);
return false;
}
if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
qDebug("DirectSound is emulated");
write_offset = 0;
return true;
}
/**
* Creates a DirectSound buffer of the required format.
*
* This function creates the buffer we'll use to play audio.
* In DirectSound there are two kinds of buffers:
* - the primary buffer: which is the actual buffer that the soundcard plays
* - the secondary buffer(s): these buffers are the one actually used by
* applications and DirectSound takes care of mixing them into the primary.
*
* Once you create a secondary buffer, you cannot change its format anymore so
* you have to release the current one and create another.
*/
bool AudioOutputDSoundPrivate::createDSoundBuffers()
{
WAVEFORMATEXTENSIBLE wformat;
// TODO: Dolby Digital AC3
ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE));
wformat.Format.cbSize = 0;
wformat.Format.nChannels = format.channels();
wformat.Format.nSamplesPerSec = format.sampleRate();
wformat.Format.wFormatTag = WAVE_FORMAT_PCM;
wformat.Format.wBitsPerSample = format.bytesPerSample() * 8;
wformat.Format.nBlockAlign = wformat.Format.nChannels * format.bytesPerSample();
wformat.Format.nAvgBytesPerSec = wformat.Format.nSamplesPerSec * wformat.Format.nBlockAlign;
wformat.dwChannelMask = channelLayoutToMS(format.channelLayoutFFmpeg());
if (format.channels() > 2) {
wformat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wformat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wformat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
wformat.Samples.wValidBitsPerSample = wformat.Format.wBitsPerSample;
}
// fill in primary sound buffer descriptor
DSBUFFERDESC dsbpridesc;
memset(&dsbpridesc, 0, sizeof(DSBUFFERDESC));
dsbpridesc.dwSize = sizeof(DSBUFFERDESC);
dsbpridesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbpridesc.dwBufferBytes = 0;
dsbpridesc.lpwfxFormat = NULL;
// create primary buffer and set its format
HRESULT res = dsound->CreateSoundBuffer(&dsbpridesc, &prim_buf, NULL);
if (res != DS_OK) {
destroy();
qWarning("Cannot create primary buffer (%s)", dserr2str(res));
return false;
}
res = prim_buf->SetFormat((WAVEFORMATEX *)&wformat);
if (res != DS_OK) {
qWarning("Cannot set primary buffer format (%s), using standard setting (bad quality)", dserr2str(res));
}
qDebug("primary buffer created");
// fill in the secondary sound buffer (=stream buffer) descriptor
DSBUFFERDESC dsbdesc;
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
| DSBCAPS_GLOBALFOCUS /** Allows background playing */
| DSBCAPS_CTRLVOLUME; /** volume control enabled */
dsbdesc.dwBufferBytes = bufferSizeTotal();
dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat;
// Needed for 5.1 on emu101k - shit soundblaster
if (format.channels() > 2)
dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE;
// now create the stream buffer
res = dsound->CreateSoundBuffer(&dsbdesc, &stream_buf, NULL);
if (res != DS_OK) {
if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) {
// Try without DSBCAPS_LOCHARDWARE
dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
res = dsound->CreateSoundBuffer(&dsbdesc, &stream_buf, NULL);
}
if (res != DS_OK) {
destroy();
qWarning("Cannot create secondary (stream)buffer (%s)", dserr2str(res));
return false;
}
}
qDebug( "Secondary (stream)buffer created");
return true;
}
} // namespace QtAV
-3
View File
@@ -166,9 +166,6 @@ static ALenum audioFormatToAL(const AudioFormat& fmt)
return format;
}
const int kBufferSize = 1024*4;
const int kBufferCount = 8;
class AudioOutputOpenALPrivate : public AudioOutputPrivate
{
public:
+5
View File
@@ -28,6 +28,7 @@ namespace QtAV {
AudioOutputId AudioOutputId_PortAudio = 1;
AudioOutputId AudioOutputId_OpenAL = 2;
AudioOutputId AudioOutputId_OpenSL = 3;
AudioOutputId AudioOutputId_DSound = 4;
QVector<AudioOutputId> GetRegistedAudioOutputIds()
{
@@ -40,6 +41,7 @@ FACTORY_DEFINE(AudioOutput)
extern void RegisterAudioOutputPortAudio_Man();
extern void RegisterAudioOutputOpenAL_Man();
extern void RegisterAudioOutputOpenSL_Man();
extern void RegisterAudioOutputDSound_Man();
void AudioOutput_RegisterAll()
{
@@ -52,6 +54,9 @@ void AudioOutput_RegisterAll()
#if QTAV_HAVE(OPENSL)
RegisterAudioOutputOpenSL_Man();
#endif //QTAV_HAVE(OPENSL)
#if QTAV_HAVE(DSOUND)
extern void RegisterAudioOutputDSound_Man();
#endif
}
} //namespace QtAV
+1
View File
@@ -37,6 +37,7 @@ Q_AV_EXPORT QVector<AudioOutputId> GetRegistedAudioOutputIds();
extern Q_AV_EXPORT AudioOutputId AudioOutputId_PortAudio;
extern Q_AV_EXPORT AudioOutputId AudioOutputId_OpenAL;
extern Q_AV_EXPORT AudioOutputId AudioOutputId_OpenSL;
extern Q_AV_EXPORT AudioOutputId AudioOutputId_DSound;
Q_AV_EXPORT void AudioOutput_RegisterAll();
+6
View File
@@ -29,8 +29,13 @@
#include <QtCore/QVector>
#include <limits>
// TODO:writeChunk(), write(QByteArray) { while writeChunk() }, users define writeChunk()
namespace QtAV {
// chunk
const int kBufferSize = 1024*4;
const int kBufferCount = 8;
class Q_AV_PRIVATE_EXPORT AudioOutputPrivate : public AVOutputPrivate
{
public:
@@ -48,6 +53,7 @@ public:
}
virtual ~AudioOutputPrivate(){}
int bufferSizeTotal() { return nb_buffers * kBufferCount; }
typedef struct {
qreal timestamp;
int data_size;
+346 -340
View File
@@ -1,340 +1,346 @@
TEMPLATE = lib
TARGET = QtAV
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4) {
QT += widgets
}
CONFIG *= qtav-buildlib
INCLUDEPATH += $$[QT_INSTALL_HEADERS]
#var with '_' can not pass to pri?
STATICLINK = 0
PROJECTROOT = $$PWD/..
!include(libQtAV.pri): error("could not find libQtAV.pri")
preparePaths($$OUT_PWD/../out)
RESOURCES += ../i18n/QtAV.qrc \
shaders/shaders.qrc
win32 {
RC_FILE = $${PROJECTROOT}/res/QtAV.rc
#no depends for rc file by default, even if rc includes a header. Makefile target use '/' as default, so not works iwth win cmd
rc.target = $$clean_path($$RC_FILE) #rc obj depends on clean path target
rc.depends = $$PWD/QtAV/version.h
#why use multiple rule failed? i.e. add a rule without command
isEmpty(QMAKE_SH) {
rc.commands = @copy /B $$system_path($$RC_FILE)+,, #change file time
} else {
rc.commands = @touch $$RC_FILE #change file time
}
QMAKE_EXTRA_TARGETS += rc
}
# copy runtime libs to qt sdk
!ios: copy_sdk_libs = $$DESTDIR/$$qtSharedLib($$NAME)
#plugin.depends = #makefile target
#windows: copy /y file1+file2+... dir. need '+'
for(f, copy_sdk_libs) {
win32: copy_sdk_libs_cmd += $$quote(-\$\(COPY_FILE\) \"$$system_path($$f)\" \"$$system_path($$[QT_INSTALL_BINS])\")
else: copy_sdk_libs_cmd += $$quote(-\$\(COPY_FILE\) \"$$system_path($$f)\" \"$$system_path($$[QT_INSTALL_LIBS])\")
}
#join values seperated by space. so quote is needed
copy_sdk_libs_cmd = $$join(copy_sdk_libs_cmd,$$escape_expand(\\n\\t))
#just append as a string to $$QMAKE_POST_LINK
isEmpty(QMAKE_POST_LINK): QMAKE_POST_LINK = $$copy_sdk_libs_cmd
else: QMAKE_POST_LINK = $${QMAKE_POST_LINK}$$escape_expand(\\n\\t)$$copy_sdk_libs_cmd
OTHER_FILES += $$RC_FILE
TRANSLATIONS = $${PROJECTROOT}/i18n/QtAV_zh_CN.ts
## sse2 sse4_1 may be defined in Qt5 qmodule.pri but is not included. Qt4 defines sse and sse2
sse4_1|config_sse4_1|contains(TARGET_ARCH_SUB, sse4.1) {
CONFIG += sse2 #only sse4.1 is checked. sse2 now can be disabled if sse4.1 is disabled
DEFINES += QTAV_HAVE_SSE4_1=1
## TODO: use SSE4_1_SOURCES
# all x64 processers supports sse2. unknown option for vc
*msvc* {
!isEqual(QT_ARCH, x86_64)|!x86_64: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE4_1
} else {
QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE4_1 #gcc -msse4.1
}
}
sse2|config_sse2|contains(TARGET_ARCH_SUB, sse2) {
DEFINES += QTAV_HAVE_SSE2=1
# all x64 processers supports sse2. unknown option for vc
*msvc* {
!isEqual(QT_ARCH, x86_64)|!x86_64: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE2
} else {
QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE2 #gcc -msse2
}
}
*msvc* {
#link FFmpeg and portaudio which are built by gcc need /SAFESEH:NO
QMAKE_LFLAGS += /SAFESEH:NO
INCLUDEPATH += compat/msvc
}
#UINT64_C: C99 math features, need -D__STDC_CONSTANT_MACROS in CXXFLAGS
DEFINES += __STDC_CONSTANT_MACROS
android: CONFIG += config_opensl
android: LIBS += -lavcodec-55 -lavformat-55 -lavutil-52 -lswscale-2
else: LIBS += -lavcodec -lavformat -lavutil -lswscale
config_avfilter {
DEFINES += QTAV_HAVE_AVFILTER=1
SOURCES += LibAVFilter.cpp
HEADERS += QtAV/LibAVFilter.h
android: LIBS += -lavfilter-4
else: LIBS += -lavfilter
}
config_swresample {
DEFINES += QTAV_HAVE_SWRESAMPLE=1
SOURCES += AudioResamplerFF.cpp
android: LIBS += -lswresample-0
else: LIBS += -lswresample
}
config_avresample {
DEFINES += QTAV_HAVE_AVRESAMPLE=1
SOURCES += AudioResamplerLibav.cpp
android: LIBS += -lavresample-1
else: LIBS += -lavresample
}
config_ipp {
DEFINES += QTAV_HAVE_IPP=1
ICCROOT = $$(IPPROOT)/../compiler
INCLUDEPATH += $$(IPPROOT)/include
SOURCES += ImageConverterIPP.cpp
message("QMAKE_TARGET.arch" $$QMAKE_TARGET.arch)
*64|contains(QMAKE_TARGET.arch, x86_64)|contains(TARGET_ARCH, x86_64) {
IPPARCH=intel64
} else {
IPPARCH=ia32
}
LIBS *= -L$$(IPPROOT)/lib/$$IPPARCH -lippcc -lippcore -lippi \
-L$$(IPPROOT)/../compiler/lib/$$IPPARCH -lsvml -limf
#omp for static link. _t is multi-thread static link
}
config_portaudio {
SOURCES += AudioOutputPortAudio.cpp
DEFINES *= QTAV_HAVE_PORTAUDIO=1
LIBS *= -lportaudio
#win32: LIBS *= -lwinmm #-lksguid #-luuid
}
config_openal {
SOURCES += AudioOutputOpenAL.cpp
DEFINES *= QTAV_HAVE_OPENAL=1
win32: LIBS += -lOpenAL32
unix:!mac:!blackberry: LIBS += -lopenal
blackberry: LIBS += -lOpenAL
mac: LIBS += -framework OpenAL
mac: DEFINES += HEADER_OPENAL_PREFIX
}
config_opensl {
SOURCES += AudioOutputOpenSL.cpp
DEFINES *= QTAV_HAVE_OPENSL=1
LIBS += -lOpenSLES
}
config_gdiplus {
DEFINES *= QTAV_HAVE_GDIPLUS=1
SOURCES += GDIRenderer.cpp
SDK_HEADERS += QtAV/GDIRenderer.h
LIBS += -lgdiplus -lGdi32
}
config_direct2d {
DEFINES *= QTAV_HAVE_DIRECT2D=1
!*msvc*: INCLUDEPATH += $$PROJECTROOT/contrib/d2d1headers
SOURCES += Direct2DRenderer.cpp
SDK_HEADERS += QtAV/Direct2DRenderer.h
#LIBS += -lD2d1
}
config_xv {
DEFINES *= QTAV_HAVE_XV=1
SOURCES += XVRenderer.cpp
SDK_HEADERS += QtAV/XVRenderer.h
LIBS += -lXv
}
config_gl {
QT *= opengl
DEFINES *= QTAV_HAVE_GL=1
SOURCES += GLWidgetRenderer.cpp GLWidgetRenderer2.cpp
SDK_HEADERS += QtAV/GLWidgetRenderer.h QtAV/GLWidgetRenderer2.h
OTHER_FILES += shaders/planar.f.glsl shaders/rgb.f.glsl
}
CONFIG += config_cuda #config_dllapi config_dllapi_cuda
#CONFIG += config_cuda_link
config_cuda {
DEFINES += QTAV_HAVE_CUDA=1
HEADERS += cuda/dllapi/nv_inc.h cuda/helper_cuda.h
SOURCES += VideoDecoderCUDA.cpp
INCLUDEPATH += $$PWD/cuda cuda/dllapi
config_dllapi:config_dllapi_cuda {
DEFINES += QTAV_HAVE_DLLAPI_CUDA=1
INCLUDEPATH += ../depends/dllapi/src
include(../depends/dllapi/src/libdllapi.pri)
SOURCES += cuda/dllapi/cuda.cpp cuda/dllapi/nvcuvid.cpp cuda/dllapi/cuviddec.cpp
} else:config_cuda_link {
DEFINES += CUDA_LINK
INCLUDEPATH += $$(CUDA_PATH)/include
LIBS += -L$$(CUDA_PATH)/lib
isEqual(TARGET_ARCH, x86): LIBS += -L$$(CUDA_PATH)/lib/Win32
else: LIBS += -L$$(CUDA_PATH)/lib/x64
LIBS += -lnvcuvid -lcuda
}
SOURCES += cuda/cuda_api.cpp
HEADERS += cuda/cuda_api.h
}
config_dxva {
DEFINES *= QTAV_HAVE_DXVA=1
SOURCES += VideoDecoderDXVA.cpp
LIBS += -lOle32
}
config_vaapi* {
DEFINES *= QTAV_HAVE_VAAPI=1
SOURCES += VideoDecoderVAAPI.cpp
LIBS += -lva #dynamic load va-glx va-x11 using dllapi
}
config_libcedarv {
DEFINES *= QTAV_HAVE_CEDARV=1
SOURCES += VideoDecoderCedarv.cpp
LIBS += -lvecore -lcedarv
}
macx:!ios: CONFIG += config_vda
config_vda {
DEFINES *= QTAV_HAVE_VDA=1
SOURCES += VideoDecoderVDA.cpp
LIBS += -framework VideoDecodeAcceleration -framework CoreVideo
}
SOURCES += \
AVCompat.cpp \
QtAV_Global.cpp \
utils/GPUMemCopy.cpp \
utils/OpenGLHelper.cpp \
QAVIOContext.cpp \
ShaderManager.cpp \
AudioThread.cpp \
AVThread.cpp \
AudioDecoder.cpp \
AudioFormat.cpp \
AudioFrame.cpp \
AudioOutput.cpp \
AudioOutputTypes.cpp \
AudioResampler.cpp \
AudioResamplerTypes.cpp \
AVDecoder.cpp \
AVDemuxer.cpp \
AVDemuxThread.cpp \
ColorTransform.cpp \
Frame.cpp \
Filter.cpp \
FilterContext.cpp \
FilterManager.cpp \
GraphicsItemRenderer.cpp \
ImageConverter.cpp \
ImageConverterFF.cpp \
QPainterRenderer.cpp \
OSD.cpp \
OSDFilter.cpp \
Packet.cpp \
AVError.cpp \
AVPlayer.cpp \
AVClock.cpp \
VideoCapture.cpp \
VideoFormat.cpp \
VideoFrame.cpp \
VideoRenderer.cpp \
VideoRendererTypes.cpp \
VideoOutput.cpp \
VideoOutputEventFilter.cpp \
WidgetRenderer.cpp \
AVOutput.cpp \
OutputSet.cpp \
OpenGLVideo.cpp \
VideoShader.cpp \
Statistics.cpp \
VideoDecoder.cpp \
VideoDecoderTypes.cpp \
VideoDecoderFFmpeg.cpp \
VideoDecoderFFmpegHW.cpp \
VideoThread.cpp \
CommonTypes.cpp
SDK_HEADERS *= \
QtAV/QtAV.h \
QtAV/dptr.h \
QtAV/QtAV_Global.h \
QtAV/AudioResampler.h \
QtAV/AudioResamplerTypes.h \
QtAV/AudioDecoder.h \
QtAV/AudioFormat.h \
QtAV/AudioFrame.h \
QtAV/AudioOutput.h \
QtAV/AudioOutputTypes.h \
QtAV/AVDecoder.h \
QtAV/AVDemuxer.h \
QtAV/CommonTypes.h \
QtAV/Filter.h \
QtAV/FilterContext.h \
QtAV/Frame.h \
QtAV/GraphicsItemRenderer.h \
QtAV/ImageConverter.h \
QtAV/ImageConverterTypes.h \
QtAV/QPainterRenderer.h \
QtAV/OSD.h \
QtAV/OSDFilter.h \
QtAV/OpenGLVideo.h \
QtAV/VideoShader.h \
QtAV/Packet.h \
QtAV/AVError.h \
QtAV/AVPlayer.h \
QtAV/VideoCapture.h \
QtAV/VideoRenderer.h \
QtAV/VideoRendererTypes.h \
QtAV/VideoOutput.h \
QtAV/WidgetRenderer.h \
QtAV/AVOutput.h \
QtAV/AVClock.h \
QtAV/VideoDecoder.h \
QtAV/VideoDecoderTypes.h \
QtAV/VideoFormat.h \
QtAV/VideoFrame.h \
QtAV/FactoryDefine.h \
QtAV/Statistics.h \
QtAV/SurfaceInterop.h \
QtAV/version.h
# QtAV/private/* may be used by developers to extend QtAV features without changing QtAV library
# headers not in QtAV/ and it's subdirs are used only by QtAV internally
HEADERS *= \
$$SDK_HEADERS \
utils/BlockingQueue.h \
utils/GPUMemCopy.h \
utils/OpenGLHelper.h \
QtAV/prepost.h \
QtAV/AVDemuxThread.h \
QtAV/AVThread.h \
QtAV/AudioThread.h \
QtAV/VideoThread.h \
QtAV/ColorTransform.h \
QtAV/VideoDecoderFFmpegHW.h \
QtAV/private/factory.h \
QtAV/private/singleton.h \
QtAV/private/AVCompat.h \
QtAV/private/AudioOutput_p.h \
QtAV/private/AudioResampler_p.h \
QtAV/private/AVThread_p.h \
QtAV/private/AVDecoder_p.h \
QtAV/private/AVOutput_p.h \
QtAV/private/Filter_p.h \
QtAV/private/FilterManager.h \
QtAV/private/Frame_p.h \
QtAV/private/ImageConverter_p.h \
QtAV/private/OutputSet.h \
QtAV/private/QAVIOContext.h \
QtAV/private/ShaderManager.h \
QtAV/private/VideoShader_p.h \
QtAV/private/VideoDecoder_p.h \
QtAV/private/VideoDecoderFFmpegHW_p.h \
QtAV/private/VideoOutputEventFilter.h \
QtAV/private/VideoRenderer_p.h \
QtAV/private/QPainterRenderer_p.h
SDK_INCLUDE_FOLDER = QtAV
include($$PROJECTROOT/deploy.pri)
TEMPLATE = lib
TARGET = QtAV
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4) {
QT += widgets
}
CONFIG *= qtav-buildlib
INCLUDEPATH += $$[QT_INSTALL_HEADERS]
#var with '_' can not pass to pri?
STATICLINK = 0
PROJECTROOT = $$PWD/..
!include(libQtAV.pri): error("could not find libQtAV.pri")
preparePaths($$OUT_PWD/../out)
RESOURCES += ../i18n/QtAV.qrc \
shaders/shaders.qrc
win32 {
RC_FILE = $${PROJECTROOT}/res/QtAV.rc
#no depends for rc file by default, even if rc includes a header. Makefile target use '/' as default, so not works iwth win cmd
rc.target = $$clean_path($$RC_FILE) #rc obj depends on clean path target
rc.depends = $$PWD/QtAV/version.h
#why use multiple rule failed? i.e. add a rule without command
isEmpty(QMAKE_SH) {
rc.commands = @copy /B $$system_path($$RC_FILE)+,, #change file time
} else {
rc.commands = @touch $$RC_FILE #change file time
}
QMAKE_EXTRA_TARGETS += rc
}
# copy runtime libs to qt sdk
!ios: copy_sdk_libs = $$DESTDIR/$$qtSharedLib($$NAME)
#plugin.depends = #makefile target
#windows: copy /y file1+file2+... dir. need '+'
for(f, copy_sdk_libs) {
win32: copy_sdk_libs_cmd += $$quote(-\$\(COPY_FILE\) \"$$system_path($$f)\" \"$$system_path($$[QT_INSTALL_BINS])\")
else: copy_sdk_libs_cmd += $$quote(-\$\(COPY_FILE\) \"$$system_path($$f)\" \"$$system_path($$[QT_INSTALL_LIBS])\")
}
#join values seperated by space. so quote is needed
copy_sdk_libs_cmd = $$join(copy_sdk_libs_cmd,$$escape_expand(\\n\\t))
#just append as a string to $$QMAKE_POST_LINK
isEmpty(QMAKE_POST_LINK): QMAKE_POST_LINK = $$copy_sdk_libs_cmd
else: QMAKE_POST_LINK = $${QMAKE_POST_LINK}$$escape_expand(\\n\\t)$$copy_sdk_libs_cmd
OTHER_FILES += $$RC_FILE
TRANSLATIONS = $${PROJECTROOT}/i18n/QtAV_zh_CN.ts
## sse2 sse4_1 may be defined in Qt5 qmodule.pri but is not included. Qt4 defines sse and sse2
sse4_1|config_sse4_1|contains(TARGET_ARCH_SUB, sse4.1) {
CONFIG += sse2 #only sse4.1 is checked. sse2 now can be disabled if sse4.1 is disabled
DEFINES += QTAV_HAVE_SSE4_1=1
## TODO: use SSE4_1_SOURCES
# all x64 processers supports sse2. unknown option for vc
*msvc* {
!isEqual(QT_ARCH, x86_64)|!x86_64: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE4_1
} else {
QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE4_1 #gcc -msse4.1
}
}
sse2|config_sse2|contains(TARGET_ARCH_SUB, sse2) {
DEFINES += QTAV_HAVE_SSE2=1
# all x64 processers supports sse2. unknown option for vc
*msvc* {
!isEqual(QT_ARCH, x86_64)|!x86_64: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE2
} else {
QMAKE_CXXFLAGS += $$QMAKE_CFLAGS_SSE2 #gcc -msse2
}
}
win32: CONFIG += config_dsound
*msvc* {
#link FFmpeg and portaudio which are built by gcc need /SAFESEH:NO
#QMAKE_LFLAGS += /SAFESEH:NO
INCLUDEPATH += compat/msvc
}
#UINT64_C: C99 math features, need -D__STDC_CONSTANT_MACROS in CXXFLAGS
DEFINES += __STDC_CONSTANT_MACROS
android: CONFIG += config_opensl
android: LIBS += -lavcodec-55 -lavformat-55 -lavutil-52 -lswscale-2
else: LIBS += -lavcodec -lavformat -lavutil -lswscale
config_avfilter {
DEFINES += QTAV_HAVE_AVFILTER=1
SOURCES += LibAVFilter.cpp
HEADERS += QtAV/LibAVFilter.h
android: LIBS += -lavfilter-4
else: LIBS += -lavfilter
}
config_swresample {
DEFINES += QTAV_HAVE_SWRESAMPLE=1
SOURCES += AudioResamplerFF.cpp
android: LIBS += -lswresample-0
else: LIBS += -lswresample
}
config_avresample {
DEFINES += QTAV_HAVE_AVRESAMPLE=1
SOURCES += AudioResamplerLibav.cpp
android: LIBS += -lavresample-1
else: LIBS += -lavresample
}
config_ipp {
DEFINES += QTAV_HAVE_IPP=1
ICCROOT = $$(IPPROOT)/../compiler
INCLUDEPATH += $$(IPPROOT)/include
SOURCES += ImageConverterIPP.cpp
message("QMAKE_TARGET.arch" $$QMAKE_TARGET.arch)
*64|contains(QMAKE_TARGET.arch, x86_64)|contains(TARGET_ARCH, x86_64) {
IPPARCH=intel64
} else {
IPPARCH=ia32
}
LIBS *= -L$$(IPPROOT)/lib/$$IPPARCH -lippcc -lippcore -lippi \
-L$$(IPPROOT)/../compiler/lib/$$IPPARCH -lsvml -limf
#omp for static link. _t is multi-thread static link
}
config_dsound {
SOURCES += AudioOutputDSound.cpp
DEFINES *= QTAV_HAVE_DSOUND=1
}
config_portaudio {
SOURCES += AudioOutputPortAudio.cpp
DEFINES *= QTAV_HAVE_PORTAUDIO=1
LIBS *= -lportaudio
#win32: LIBS *= -lwinmm #-lksguid #-luuid
}
config_openal {
SOURCES += AudioOutputOpenAL.cpp
DEFINES *= QTAV_HAVE_OPENAL=1
win32: LIBS += -lOpenAL32
unix:!mac:!blackberry: LIBS += -lopenal
blackberry: LIBS += -lOpenAL
mac: LIBS += -framework OpenAL
mac: DEFINES += HEADER_OPENAL_PREFIX
}
config_opensl {
SOURCES += AudioOutputOpenSL.cpp
DEFINES *= QTAV_HAVE_OPENSL=1
LIBS += -lOpenSLES
}
config_gdiplus {
DEFINES *= QTAV_HAVE_GDIPLUS=1
SOURCES += GDIRenderer.cpp
SDK_HEADERS += QtAV/GDIRenderer.h
LIBS += -lgdiplus -lgdi32
}
config_direct2d {
DEFINES *= QTAV_HAVE_DIRECT2D=1
!*msvc*: INCLUDEPATH += $$PROJECTROOT/contrib/d2d1headers
SOURCES += Direct2DRenderer.cpp
SDK_HEADERS += QtAV/Direct2DRenderer.h
#LIBS += -lD2d1
}
config_xv {
DEFINES *= QTAV_HAVE_XV=1
SOURCES += XVRenderer.cpp
SDK_HEADERS += QtAV/XVRenderer.h
LIBS += -lXv
}
config_gl {
QT *= opengl
DEFINES *= QTAV_HAVE_GL=1
SOURCES += GLWidgetRenderer.cpp GLWidgetRenderer2.cpp
SDK_HEADERS += QtAV/GLWidgetRenderer.h QtAV/GLWidgetRenderer2.h
OTHER_FILES += shaders/planar.f.glsl shaders/rgb.f.glsl
}
CONFIG += config_cuda #config_dllapi config_dllapi_cuda
#CONFIG += config_cuda_link
config_cuda {
DEFINES += QTAV_HAVE_CUDA=1
HEADERS += cuda/dllapi/nv_inc.h cuda/helper_cuda.h
SOURCES += VideoDecoderCUDA.cpp
INCLUDEPATH += $$PWD/cuda cuda/dllapi
config_dllapi:config_dllapi_cuda {
DEFINES += QTAV_HAVE_DLLAPI_CUDA=1
INCLUDEPATH += ../depends/dllapi/src
include(../depends/dllapi/src/libdllapi.pri)
SOURCES += cuda/dllapi/cuda.cpp cuda/dllapi/nvcuvid.cpp cuda/dllapi/cuviddec.cpp
} else:config_cuda_link {
DEFINES += CUDA_LINK
INCLUDEPATH += $$(CUDA_PATH)/include
LIBS += -L$$(CUDA_PATH)/lib
isEqual(TARGET_ARCH, x86): LIBS += -L$$(CUDA_PATH)/lib/Win32
else: LIBS += -L$$(CUDA_PATH)/lib/x64
LIBS += -lnvcuvid -lcuda
}
SOURCES += cuda/cuda_api.cpp
HEADERS += cuda/cuda_api.h
}
config_dxva {
DEFINES *= QTAV_HAVE_DXVA=1
SOURCES += VideoDecoderDXVA.cpp
LIBS += -lole32
}
config_vaapi* {
DEFINES *= QTAV_HAVE_VAAPI=1
SOURCES += VideoDecoderVAAPI.cpp
LIBS += -lva #dynamic load va-glx va-x11 using dllapi
}
config_libcedarv {
DEFINES *= QTAV_HAVE_CEDARV=1
SOURCES += VideoDecoderCedarv.cpp
LIBS += -lvecore -lcedarv
}
macx:!ios: CONFIG += config_vda
config_vda {
DEFINES *= QTAV_HAVE_VDA=1
SOURCES += VideoDecoderVDA.cpp
LIBS += -framework VideoDecodeAcceleration -framework CoreVideo
}
SOURCES += \
AVCompat.cpp \
QtAV_Global.cpp \
utils/GPUMemCopy.cpp \
utils/OpenGLHelper.cpp \
QAVIOContext.cpp \
ShaderManager.cpp \
AudioThread.cpp \
AVThread.cpp \
AudioDecoder.cpp \
AudioFormat.cpp \
AudioFrame.cpp \
AudioOutput.cpp \
AudioOutputTypes.cpp \
AudioResampler.cpp \
AudioResamplerTypes.cpp \
AVDecoder.cpp \
AVDemuxer.cpp \
AVDemuxThread.cpp \
ColorTransform.cpp \
Frame.cpp \
Filter.cpp \
FilterContext.cpp \
FilterManager.cpp \
GraphicsItemRenderer.cpp \
ImageConverter.cpp \
ImageConverterFF.cpp \
QPainterRenderer.cpp \
OSD.cpp \
OSDFilter.cpp \
Packet.cpp \
AVError.cpp \
AVPlayer.cpp \
AVClock.cpp \
VideoCapture.cpp \
VideoFormat.cpp \
VideoFrame.cpp \
VideoRenderer.cpp \
VideoRendererTypes.cpp \
VideoOutput.cpp \
VideoOutputEventFilter.cpp \
WidgetRenderer.cpp \
AVOutput.cpp \
OutputSet.cpp \
OpenGLVideo.cpp \
VideoShader.cpp \
Statistics.cpp \
VideoDecoder.cpp \
VideoDecoderTypes.cpp \
VideoDecoderFFmpeg.cpp \
VideoDecoderFFmpegHW.cpp \
VideoThread.cpp \
CommonTypes.cpp
SDK_HEADERS *= \
QtAV/QtAV.h \
QtAV/dptr.h \
QtAV/QtAV_Global.h \
QtAV/AudioResampler.h \
QtAV/AudioResamplerTypes.h \
QtAV/AudioDecoder.h \
QtAV/AudioFormat.h \
QtAV/AudioFrame.h \
QtAV/AudioOutput.h \
QtAV/AudioOutputTypes.h \
QtAV/AVDecoder.h \
QtAV/AVDemuxer.h \
QtAV/CommonTypes.h \
QtAV/Filter.h \
QtAV/FilterContext.h \
QtAV/Frame.h \
QtAV/GraphicsItemRenderer.h \
QtAV/ImageConverter.h \
QtAV/ImageConverterTypes.h \
QtAV/QPainterRenderer.h \
QtAV/OSD.h \
QtAV/OSDFilter.h \
QtAV/OpenGLVideo.h \
QtAV/VideoShader.h \
QtAV/Packet.h \
QtAV/AVError.h \
QtAV/AVPlayer.h \
QtAV/VideoCapture.h \
QtAV/VideoRenderer.h \
QtAV/VideoRendererTypes.h \
QtAV/VideoOutput.h \
QtAV/WidgetRenderer.h \
QtAV/AVOutput.h \
QtAV/AVClock.h \
QtAV/VideoDecoder.h \
QtAV/VideoDecoderTypes.h \
QtAV/VideoFormat.h \
QtAV/VideoFrame.h \
QtAV/FactoryDefine.h \
QtAV/Statistics.h \
QtAV/SurfaceInterop.h \
QtAV/version.h
# QtAV/private/* may be used by developers to extend QtAV features without changing QtAV library
# headers not in QtAV/ and it's subdirs are used only by QtAV internally
HEADERS *= \
$$SDK_HEADERS \
utils/BlockingQueue.h \
utils/GPUMemCopy.h \
utils/OpenGLHelper.h \
QtAV/prepost.h \
QtAV/AVDemuxThread.h \
QtAV/AVThread.h \
QtAV/AudioThread.h \
QtAV/VideoThread.h \
QtAV/ColorTransform.h \
QtAV/VideoDecoderFFmpegHW.h \
QtAV/private/factory.h \
QtAV/private/singleton.h \
QtAV/private/AVCompat.h \
QtAV/private/AudioOutput_p.h \
QtAV/private/AudioResampler_p.h \
QtAV/private/AVThread_p.h \
QtAV/private/AVDecoder_p.h \
QtAV/private/AVOutput_p.h \
QtAV/private/Filter_p.h \
QtAV/private/FilterManager.h \
QtAV/private/Frame_p.h \
QtAV/private/ImageConverter_p.h \
QtAV/private/OutputSet.h \
QtAV/private/QAVIOContext.h \
QtAV/private/ShaderManager.h \
QtAV/private/VideoShader_p.h \
QtAV/private/VideoDecoder_p.h \
QtAV/private/VideoDecoderFFmpegHW_p.h \
QtAV/private/VideoOutputEventFilter.h \
QtAV/private/VideoRenderer_p.h \
QtAV/private/QPainterRenderer_p.h
SDK_INCLUDE_FOLDER = QtAV
include($$PROJECTROOT/deploy.pri)