CHANGE(client): Remove bundled Opus version

Since newer versions of libsndfile now link to the system-wide installed
Opus version (on Linux distros), there would be a conflict between
symbols defined in the system-wide installation and the self-built
(bundled) Opus library.
This, as it turned out, can lead for instance to failing assertions due
to pointer identities no longer holding because e.g. a function from the
self-built Opus version ends up calling a sub-function from the
system-wide Opus installation instead. This is also the root cause for
the infamous "Opus crash" or "Audio wizard crash".

In order to avoid these issues, we don't ship a bundled Opus version
anymore and instead make it a build prerequisite to have Opus installed
on your system.

Fixes #5302
This commit is contained in:
Robert Adam 2022-12-27 19:49:57 +01:00
parent ca79e9a470
commit d11fd05062
19 changed files with 59 additions and 310 deletions

View File

@ -12,6 +12,6 @@ sudo apt-get -y install build-essential g++-multilib ninja-build pkg-config \
libboost-dev libssl-dev libprotobuf-dev protobuf-compiler libprotoc-dev \
libcap-dev libxi-dev \
libasound2-dev libasound2-plugins libasound2-plugins-extra\
libogg-dev libsndfile1-dev libspeechd-dev \
libogg-dev libsndfile1-dev libopus-dev libspeechd-dev \
libavahi-compat-libdnssd-dev libzeroc-ice-dev \
zsync appstream libpoco-dev

View File

@ -8,7 +8,7 @@ freebsd_instance:
freebsd_task:
pkg_script:
- pkg update && pkg upgrade -y
- pkg install -y git ninja pkgconf cmake qt5-buildtools qt5-qmake qt5-linguisttools qt5-concurrent qt5-network qt5-xml qt5-sql qt5-svg qt5-testlib boost-libs libsndfile protobuf ice avahi-libdns poco
- pkg install -y git ninja pkgconf cmake qt5-buildtools qt5-qmake qt5-linguisttools qt5-concurrent qt5-network qt5-xml qt5-sql qt5-svg qt5-testlib boost-libs libsndfile protobuf ice avahi-libdns poco opus
fetch_submodules_script: git submodule --quiet update --init --recursive
build_script:
- mkdir build && cd build

View File

@ -23,6 +23,7 @@ sudo apt -y install \
libasound2-plugins-extra \
libogg-dev \
libsndfile1-dev \
libopus-dev \
libspeechd-dev \
libavahi-compat-libdnssd-dev \
libzeroc-ice-dev \

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "3rdparty/opus"]
path = 3rdparty/opus
url = https://github.com/mumble-voip/opus.git
[submodule "3rdparty/minhook"]
path = 3rdparty/minhook
url = https://github.com/mumble-voip/minhook.git

1
3rdparty/opus vendored

@ -1 +0,0 @@
Subproject commit 027cd11ed732d02456d1f049081d24b4626d5cb9

View File

@ -28,6 +28,7 @@ sudo apt install \
libasound2-dev \
libogg-dev \
libsndfile1-dev \
libopus-dev \
libspeechd-dev \
libavahi-compat-libdnssd-dev \
libxcb-xinerama0 \

View File

@ -44,11 +44,6 @@ Use the bundled GSL version instead of looking for one on the system
Build the included version of nlohmann_json instead of looking for one on the system
(Default: ON)
### bundled-opus
Build the included version of Opus instead of looking for one on the system.
(Default: ON)
### bundled-rnnoise
Build the included version of RNNoise instead of looking for one on the system.
@ -134,21 +129,6 @@ Whether or not tests that need a working internet connection should be included
Build a heavily optimized version, specific to the machine it's being compiled on.
(Default: OFF)
### OPUS_BUILD_SHARED_LIBRARY
(No description available)
(Default: ON)
### OPUS_BUILD_TESTS
(No description available)
(Default: OFF)
### OPUS_STACK_PROTECTOR
(No description available)
(Default: OFF)
### oss
Build support for OSS.

View File

@ -86,7 +86,6 @@ public class ClientInstaller : MumbleInstall {
// 64 bit
this.Platform = WixSharp.Platform.x64;
binaries = new List<string>() {
"opus.dll",
"rnnoise.dll",
"speexdsp.dll",
"mumble.exe",
@ -107,7 +106,6 @@ public class ClientInstaller : MumbleInstall {
// 32 bit
this.Platform = WixSharp.Platform.x86;
binaries = new List<string>() {
"opus.dll",
"rnnoise.dll",
"speexdsp.dll",
"mumble.exe",

View File

@ -22,7 +22,6 @@ repoRoot = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
# List of (3rdparty) licenses [<variableName>, <pathToLicenseFile>, <DisplayName>, <URL>, (<define-guard>)?]
licenses = [
["licenseMumble", "LICENSE", "Mumble", "https://www.mumble.info/"],
["licenseOpus", "3rdparty/opus/COPYING", "Opus", "http://www.opus-codec.org/"],
["licenseSPEEXDSP", "3rdparty/speexdsp/COPYING", "SpeexDSP", "http://www.speex.org/"],
["licenseOpenSSL", "3rdPartyLicenses/openssl_license.txt", "OpenSSL", "http://www.openssl.org/"],
["licenseLibsndfile", "3rdPartyLicenses/libsndfile_license.txt", "libsndfile", "http://www.mega-nerd.com/libsndfile/"],

View File

@ -8,7 +8,6 @@
#include "AudioInput.h"
#include "AudioOutput.h"
#include "Log.h"
#include "OpusCodec.h"
#include "PacketDataStream.h"
#include "PluginManager.h"
#include "Global.h"
@ -18,33 +17,9 @@
#include <cstring>
class CodecInit : public DeferInit {
public:
void initialize();
void destroy();
};
#define DOUBLE_RAND (rand() / static_cast< double >(RAND_MAX))
LoopUser LoopUser::lpLoopy;
CodecInit ciInit;
void CodecInit::initialize() {
OpusCodec *oCodec = new OpusCodec();
if (oCodec->isValid()) {
oCodec->report();
Global::get().oCodec = oCodec;
} else {
Log::logOrDefer(
Log::CriticalError,
QObject::tr("CodecInit: Failed to load Opus, it will not be available for encoding/decoding audio."));
delete oCodec;
}
}
void CodecInit::destroy() {
delete Global::get().oCodec;
}
LoopUser::LoopUser() {
qsName = QLatin1String("Loopy");

View File

@ -10,7 +10,6 @@
#include "MainWindow.h"
#include "MumbleProtocol.h"
#include "NetworkConfig.h"
#include "OpusCodec.h"
#include "PacketDataStream.h"
#include "PluginManager.h"
#include "ServerHandler.h"
@ -19,6 +18,8 @@
#include "VoiceRecorder.h"
#include "Global.h"
#include <opus.h>
#ifdef USE_RNNOISE
extern "C" {
# include "rnnoise.h"
@ -228,25 +229,21 @@ AudioInput::AudioInput() : opusBuffer(Global::get().s.iFramesPerPacket * (SAMPLE
m_codec = Mumble::Protocol::AudioCodec::Opus;
activityState = ActivityStateActive;
oCodec = nullptr;
opusState = nullptr;
oCodec = Global::get().oCodec;
if (oCodec) {
if (bAllowLowDelay && iAudioQuality >= 64000) { // > 64 kbit/s bitrate and low delay allowed
opusState = oCodec->opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_RESTRICTED_LOWDELAY, nullptr);
qWarning("AudioInput: Opus encoder set for low delay");
} else if (iAudioQuality >= 32000) { // > 32 kbit/s bitrate
opusState = oCodec->opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_AUDIO, nullptr);
qWarning("AudioInput: Opus encoder set for high quality speech");
} else {
opusState = oCodec->opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, nullptr);
qWarning("AudioInput: Opus encoder set for low quality speech");
}
oCodec->opus_encoder_ctl(opusState, OPUS_SET_VBR(0)); // CBR
if (bAllowLowDelay && iAudioQuality >= 64000) { // > 64 kbit/s bitrate and low delay allowed
opusState = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_RESTRICTED_LOWDELAY, nullptr);
qWarning("AudioInput: Opus encoder set for low delay");
} else if (iAudioQuality >= 32000) { // > 32 kbit/s bitrate
opusState = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_AUDIO, nullptr);
qWarning("AudioInput: Opus encoder set for high quality speech");
} else {
opusState = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, nullptr);
qWarning("AudioInput: Opus encoder set for low quality speech");
}
opus_encoder_ctl(opusState, OPUS_SET_VBR(0)); // CBR
#ifdef USE_RNNOISE
denoiseState = rnnoise_create(nullptr);
#endif
@ -296,7 +293,7 @@ AudioInput::~AudioInput() {
wait();
if (opusState) {
oCodec->opus_encoder_destroy(opusState);
opus_encoder_destroy(opusState);
}
#ifdef USE_RNNOISE
@ -846,18 +843,14 @@ void AudioInput::selectNoiseCancel() {
int AudioInput::encodeOpusFrame(short *source, int size, EncodingOutputBuffer &buffer) {
int len;
if (!oCodec) {
return 0;
}
if (bResetEncoder) {
oCodec->opus_encoder_ctl(opusState, OPUS_RESET_STATE, nullptr);
opus_encoder_ctl(opusState, OPUS_RESET_STATE, nullptr);
bResetEncoder = false;
}
oCodec->opus_encoder_ctl(opusState, OPUS_SET_BITRATE(iAudioQuality));
opus_encoder_ctl(opusState, OPUS_SET_BITRATE(iAudioQuality));
len = oCodec->opus_encode(opusState, source, size, &buffer[0], static_cast< opus_int32 >(buffer.size()));
len = opus_encode(opusState, source, size, &buffer[0], static_cast< opus_int32 >(buffer.size()));
const int tenMsFrameCount = (size / iFrameSize);
iBitrate = (len * 100 * 8) / tenMsFrameCount;
return len;

View File

@ -30,7 +30,6 @@
#include "Timer.h"
class AudioInput;
class OpusCodec;
struct OpusEncoder;
struct DenoiseState;
typedef boost::shared_ptr< AudioInput > AudioInputPtr;
@ -187,7 +186,6 @@ private:
inMixerFunc chooseMixer(const unsigned int nchan, SampleFormat sf, quint64 mask);
void resetAudioProcessor();
OpusCodec *oCodec;
OpusEncoder *opusState;
DenoiseState *denoiseState;
bool selectCodec();

View File

@ -7,11 +7,12 @@
#include "Audio.h"
#include "ClientUser.h"
#include "OpusCodec.h"
#include "PacketDataStream.h"
#include "Utils.h"
#include "Global.h"
#include <opus.h>
#include <algorithm>
#include <cassert>
#include <cmath>
@ -59,7 +60,6 @@ AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, Mumble
: AudioOutputUser(user->qsName), iMixerFreq(freq), m_codec(codec), p(user) {
int err;
oCodec = nullptr;
opusState = nullptr;
bHasTerminator = false;
@ -80,13 +80,10 @@ AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, Mumble
// Always pretend Stereo mode is true by default. since opus will convert mono stream to stereo stream.
// https://tools.ietf.org/html/rfc6716#section-2.1.2
bStereo = true;
oCodec = Global::get().oCodec;
if (oCodec) {
opusState = oCodec->opus_decoder_create(iSampleRate, bStereo ? 2 : 1, nullptr);
oCodec->opus_decoder_ctl(
opusState, OPUS_SET_PHASE_INVERSION_DISABLED(1)); // Disable phase inversion for better mono downmix.
}
bStereo = true;
opusState = opus_decoder_create(iSampleRate, bStereo ? 2 : 1, nullptr);
opus_decoder_ctl(opusState,
OPUS_SET_PHASE_INVERSION_DISABLED(1)); // Disable phase inversion for better mono downmix.
// iAudioBufferSize: size (in unit of float) of the buffer used to store decoded pcm data.
// For opus, the maximum frame size of a packet is 60ms.
@ -157,7 +154,7 @@ AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, Mumble
AudioOutputSpeech::~AudioOutputSpeech() {
if (opusState) {
oCodec->opus_decoder_destroy(opusState);
opus_decoder_destroy(opusState);
}
if (srs)
@ -186,12 +183,9 @@ void AudioOutputSpeech::addFrameToBuffer(const Mumble::Protocol::AudioData &audi
assert(m_codec == Mumble::Protocol::AudioCodec::Opus);
assert(audioData.usedCodec == m_codec);
if (oCodec) {
samples =
oCodec->opus_decoder_get_nb_samples(opusState, audioData.payload.data(),
audioData.payload.size()); // this function return samples per channel
samples *= 2; // since we assume all input stream is stereo.
}
samples = opus_decoder_get_nb_samples(opusState, audioData.payload.data(),
audioData.payload.size()); // this function return samples per channel
samples *= 2; // since we assume all input stream is stereo.
// We can't handle frames which are not a multiple of our configured framesize.
if (samples % iFrameSize != 0) {
@ -334,28 +328,25 @@ bool AudioOutputSpeech::prepareSampleBuffer(unsigned int frameCount) {
assert(m_codec == Mumble::Protocol::AudioCodec::Opus);
if (oCodec) {
if (qba.isEmpty() || !(p && p->bLocalMute)) {
// If qba is empty, we have to let Opus know about the packet loss
// Otherwise if the associated user is not locally muted, we want to decode the audio
// packet normally in order to be able to play it.
decodedSamples = oCodec->opus_decode_float(
opusState,
qba.isEmpty() ? nullptr : reinterpret_cast< const unsigned char * >(qba.constData()),
qba.size(), pOut, iAudioBufferSize, 0);
} else {
// If the packet is non-empty, but the associated user is locally muted,
// we don't have to decode the packet. Instead it is enough to know how many
// samples it contained so that we can then mute the appropriate output length
decodedSamples = oCodec->opus_packet_get_samples_per_frame(
reinterpret_cast< const unsigned char * >(qba.constData()), SAMPLE_RATE);
}
// The returned sample count we get from the Opus functions refer to samples per channel.
// Thus in order to get the total amount, we have to multiply by the channel count.
decodedSamples *= channels;
if (qba.isEmpty() || !(p && p->bLocalMute)) {
// If qba is empty, we have to let Opus know about the packet loss
// Otherwise if the associated user is not locally muted, we want to decode the audio
// packet normally in order to be able to play it.
decodedSamples = opus_decode_float(
opusState, qba.isEmpty() ? nullptr : reinterpret_cast< const unsigned char * >(qba.constData()),
qba.size(), pOut, iAudioBufferSize, 0);
} else {
// If the packet is non-empty, but the associated user is locally muted,
// we don't have to decode the packet. Instead it is enough to know how many
// samples it contained so that we can then mute the appropriate output length
decodedSamples = opus_packet_get_samples_per_frame(
reinterpret_cast< const unsigned char * >(qba.constData()), SAMPLE_RATE);
}
// The returned sample count we get from the Opus functions refer to samples per channel.
// Thus in order to get the total amount, we have to multiply by the channel count.
decodedSamples *= channels;
if (decodedSamples < 0) {
decodedSamples = iFrameSize;
memset(pOut, 0, iFrameSize * sizeof(float));
@ -395,10 +386,8 @@ bool AudioOutputSpeech::prepareSampleBuffer(unsigned int frameCount) {
}
} else {
assert(m_codec == Mumble::Protocol::AudioCodec::Opus);
if (oCodec) {
decodedSamples = oCodec->opus_decode_float(opusState, nullptr, 0, pOut, iFrameSize, 0);
decodedSamples *= channels;
}
decodedSamples = opus_decode_float(opusState, nullptr, 0, pOut, iFrameSize, 0);
decodedSamples *= channels;
if (decodedSamples < 0) {
decodedSamples = iFrameSize;

View File

@ -18,7 +18,6 @@
#include <mutex>
#include <vector>
class OpusCodec;
class ClientUser;
struct OpusDecoder;
@ -55,7 +54,6 @@ protected:
JitterBuffer *jbJitter;
int iMissCount;
OpusCodec *oCodec;
OpusDecoder *opusState;
QList< QByteArray > qlFrames;

View File

@ -22,7 +22,6 @@ option(update "Check for updates by default." ON)
option(translations "Include languages other than English." ON)
option(bundle-qt-translations "Bundle Qt's translations as well" ${static})
option(bundled-opus "Build the included version of Opus instead of looking for one on the system." ON)
option(bundled-speex "Build the included version of Speex instead of looking for one on the system." ON)
option(rnnoise "Use RNNoise for machine learning noise reduction." ON)
option(bundled-rnnoise "Build the included version of RNNoise instead of looking for one on the system." ${rnnoise})
@ -184,8 +183,6 @@ set(MUMBLE_SOURCES
"NetworkConfig.cpp"
"NetworkConfig.h"
"NetworkConfig.ui"
"OpusCodec.cpp"
"OpusCodec.h"
"PluginConfig.cpp"
"PluginConfig.h"
"PluginConfig.ui"
@ -694,37 +691,15 @@ else()
endif()
endif()
if(bundled-opus)
option(OPUS_BUILD_SHARED_LIBRARY "" ON)
option(OPUS_BUILD_TESTS "" OFF)
if(MINGW)
option(OPUS_STACK_PROTECTOR "" OFF)
endif()
add_subdirectory("${3RDPARTY_DIR}/opus" "${CMAKE_CURRENT_BINARY_DIR}/opus" EXCLUDE_FROM_ALL)
# Disable all warnings that the Opus code may emit
disable_warnings_for_all_targets_in("${3RDPARTY_DIR}/opus")
# Opus doesn't work in unity builds
set_target_properties(opus PROPERTIES UNITY_BUILD FALSE)
add_dependencies(mumble_client_object_lib opus)
target_include_directories(mumble_client_object_lib PUBLIC "${3RDPARTY_DIR}/opus/include")
if(WIN32)
# Shared library on Windows (e.g. ".dll")
set_target_properties(opus PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
else()
# Shared library on UNIX (e.g. ".so")
set_target_properties(opus PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
endif()
install_library(opus mumble_client)
else()
find_pkg(opus REQUIRED)
target_include_directories(mumble_client_object_lib PUBLIC ${opus_INCLUDE_DIRS})
find_pkg("opus;Opus" REQUIRED)
target_include_directories(mumble_client_object_lib PUBLIC ${opus_INCLUDE_DIRS})
target_link_libraries(mumble_client_object_lib PUBLIC ${opus_LIBRARIES})
if(TARGET opus)
target_link_libraries(mumble_client_object_lib PUBLIC opus)
elseif(TARGET Opus)
target_link_libraries(mumble_client_object_lib PUBLIC Opus)
elseif(TARGET Opus::opus)
target_link_libraries(mumble_client_object_lib PUBLIC Opus::opus)
endif()
if(bundled-speex)

View File

@ -31,7 +31,6 @@ class Overlay;
class LCD;
class Zeroconf;
class OverlayClient;
class OpusCodec;
class LogEmitter;
class DeveloperConsole;
class TalkingUI;
@ -96,7 +95,6 @@ public:
int iMaxBandwidth;
int iAudioBandwidth;
QDir qdBasePath;
OpusCodec *oCodec;
bool bAttenuateOthers;
/// If set the AudioOutput::mix will forcefully adjust the volume of all
/// non-priority speakers.

View File

@ -21,7 +21,6 @@
#include "Log.h"
#include "Net.h"
#include "NetworkConfig.h"
#include "OpusCodec.h"
#include "GlobalShortcut.h"
#include "GlobalShortcutTypes.h"
#ifdef USE_OVERLAY

View File

@ -1,100 +0,0 @@
// Copyright 2018-2022 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "OpusCodec.h"
#include "Audio.h"
#include "MumbleApplication.h"
#include "Version.h"
#ifdef Q_CC_GNU
# define RESOLVE(var) \
{ \
var = reinterpret_cast< __typeof__(var) >(qlOpus.resolve(#var)); \
bValid = bValid && var; \
}
#else
# define RESOLVE(var) \
{ \
*reinterpret_cast< void ** >(&var) = static_cast< void * >(qlOpus.resolve(#var)); \
bValid = bValid && var; \
}
#endif
OpusCodec::OpusCodec() {
bValid = false;
qlOpus.setLoadHints(QLibrary::ResolveAllSymbolsHint);
QStringList alternatives;
#if defined(Q_OS_MAC)
alternatives << QString::fromLatin1("libopus0.dylib");
alternatives << QString::fromLatin1("opus0.dylib");
alternatives << QString::fromLatin1("libopus.dylib");
alternatives << QString::fromLatin1("opus.dylib");
#elif defined(Q_OS_UNIX)
alternatives << QString::fromLatin1("libopus.so.0");
alternatives << QString::fromLatin1("libopus0.so");
alternatives << QString::fromLatin1("libopus.so");
alternatives << QString::fromLatin1("opus.so");
#else
alternatives << QString::fromLatin1("opus0.dll");
alternatives << QString::fromLatin1("opus.dll");
#endif
QStringList basePaths;
basePaths << MumbleApplication::instance()->applicationVersionRootPath() + "/";
#ifdef Q_OS_MAC
basePaths << QApplication::instance()->applicationDirPath() + "/../Codecs/";
#endif
#ifdef MUMBLE_LIBRARY_PATH
basePaths << MUMTEXT(MUMBLE_LIBRARY_PATH) "/";
#endif
basePaths << ""; // General search without telling it a particular path to look for
for (const QString &basePath : basePaths) {
for (const QString &libName : alternatives) {
qlOpus.setFileName(basePath + libName);
if (qlOpus.load()) {
bValid = true;
break;
}
}
if (bValid) {
break;
}
}
RESOLVE(opus_get_version_string);
RESOLVE(opus_encode);
RESOLVE(opus_decode_float);
RESOLVE(opus_encoder_create);
RESOLVE(opus_encoder_ctl);
RESOLVE(opus_encoder_destroy);
RESOLVE(opus_decoder_create);
RESOLVE(opus_decoder_ctl);
RESOLVE(opus_decoder_destroy);
RESOLVE(opus_decoder_get_nb_samples);
RESOLVE(opus_packet_get_samples_per_frame);
}
OpusCodec::~OpusCodec() {
qlOpus.unload();
}
bool OpusCodec::isValid() const {
return bValid;
}
void OpusCodec::report() const {
qDebug("%s from %s", opus_get_version_string(), qPrintable(qlOpus.fileName()));
}
#undef RESOLVE

View File

@ -1,51 +0,0 @@
// Copyright 2018-2022 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MUMBLE_OPUSCODEC_H_
#define MUMBLE_MUMBLE_OPUSCODEC_H_
#include <opus.h>
#include <QtCore/QLibrary>
#ifndef Q_OS_WIN
# define __cdecl
#endif
/// Loads Opus from a shared library and acts as a wrapper for its functions.
class OpusCodec {
private:
Q_DISABLE_COPY(OpusCodec)
protected:
QLibrary qlOpus;
bool bValid;
public:
OpusCodec();
virtual ~OpusCodec();
bool isValid() const;
void report() const;
const char *(__cdecl *opus_get_version_string)();
OpusEncoder *(__cdecl *opus_encoder_create)(opus_int32 Fs, int channels, int application, int *error);
int(__cdecl *opus_encoder_ctl)(OpusEncoder *st, int request, ...);
void(__cdecl *opus_encoder_destroy)(OpusEncoder *st);
OpusDecoder *(__cdecl *opus_decoder_create)(opus_int32 Fs, int channels, int *error);
int(__cdecl *opus_decoder_ctl)(OpusDecoder *st, int request, ...);
void(__cdecl *opus_decoder_destroy)(OpusDecoder *st);
int(__cdecl *opus_encode)(OpusEncoder *st, const opus_int16 *pcm, int frame_size, unsigned char *compressed,
int nbCompressedBytes);
int(__cdecl *opus_decode_float)(OpusDecoder *st, const unsigned char *data, opus_int32 len, float *pcm,
int frame_size, int decode_fec);
int(__cdecl *opus_decoder_get_nb_samples)(OpusDecoder *st, const unsigned char packet[], opus_int32 len);
int(__cdecl *opus_packet_get_samples_per_frame)(const unsigned char *data, opus_int32 Fs);
};
#endif // OPUSCODEC_H_