diff --git a/3rdparty/opus-build/opus-build.pro b/3rdparty/opus-build/opus-build.pro index 61047e4f3..86d973c78 100644 --- a/3rdparty/opus-build/opus-build.pro +++ b/3rdparty/opus-build/opus-build.pro @@ -45,19 +45,6 @@ win32 { } unix { - CONFIG += staticlib - CONFIG(sbcelt) { - # Before Opus 1.1 we used to be able to build Opus - # as C++ code to get C++ name mangling for free, - # allowing us to statically build both libcelt - # and libopus into the same binary while avoiding - # symbol clashes between the two libraries. - # - # Stock Opus 1.1 doesn't build in C++ mode, so error - # out for now. - error(Mumble cannot be built in SBCELT mode with Opus 1.1 - aborting build.) - } - contains(QMAKE_CFLAGS, -ffast-math) { DEFINES += FLOAT_APPROX } diff --git a/src/mumble/Audio.cpp b/src/mumble/Audio.cpp index dec81aa6e..865e96227 100644 --- a/src/mumble/Audio.cpp +++ b/src/mumble/Audio.cpp @@ -10,6 +10,7 @@ #include "AudioInput.h" #include "AudioOutput.h" #include "CELTCodec.h" +#include "OpusCodec.h" #include "Global.h" #include "PacketDataStream.h" @@ -25,13 +26,24 @@ LoopUser LoopUser::lpLoopy; CodecInit ciInit; void CodecInit::initialize() { - CELTCodec *codec = NULL; +#ifdef USE_OPUS + OpusCodec *oCodec = new OpusCodec(); + if (oCodec->isValid()) { + oCodec->report(); + g.oCodec = oCodec; + } else { + qWarning("CodecInit: Failed to load Opus, it will not be available for encoding/decoding audio."); + delete oCodec; + } +#endif if (g.s.bDisableCELT) { // Kill switch for CELT activated. Do not initialize it. return; } + CELTCodec *codec = NULL; + #ifdef USE_SBCELT codec = new CELTCodecSBCELT(); if (codec->isValid()) { @@ -74,6 +86,10 @@ void CodecInit::initialize() { } void CodecInit::destroy() { +#ifdef USE_OPUS + delete g.oCodec; +#endif + foreach(CELTCodec *codec, g.qmCodecs) delete codec; g.qmCodecs.clear(); diff --git a/src/mumble/AudioInput.cpp b/src/mumble/AudioInput.cpp index fb7067694..1c5f208af 100644 --- a/src/mumble/AudioInput.cpp +++ b/src/mumble/AudioInput.cpp @@ -9,6 +9,7 @@ #include "AudioOutput.h" #include "CELTCodec.h" +#include "OpusCodec.h" #include "ServerHandler.h" #include "MainWindow.h" #include "User.h" @@ -19,10 +20,6 @@ #include "NetworkConfig.h" #include "VoiceRecorder.h" -#ifdef USE_OPUS -#include "opus.h" -#endif - // Remember that we cannot use static member classes that are not pointers, as the constructor // for AudioInputRegistrar() might be called before they are initialized, as the constructor // is called from global initialization. @@ -86,15 +83,18 @@ AudioInput::AudioInput() : opusBuffer(g.s.iFramesPerPacket * (SAMPLE_RATE / 100) iFrameSize = SAMPLE_RATE / 100; #ifdef USE_OPUS - if (!g.s.bUseOpusMusicEncoding) { - opusState = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, NULL); - qWarning("AudioInput: Opus encoder set for VOIP"); - } else { - opusState = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_AUDIO, NULL); - qWarning("AudioInput: Opus encoder set for Music"); - } + oCodec = g.oCodec; + if (oCodec) { + if (!g.s.bUseOpusMusicEncoding) { + opusState = oCodec->opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, NULL); + qWarning("AudioInput: Opus encoder set for VOIP"); + } else { + opusState = oCodec->opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_AUDIO, NULL); + qWarning("AudioInput: Opus encoder set for Music"); + } - opus_encoder_ctl(opusState, OPUS_SET_VBR(0)); // CBR + oCodec->opus_encoder_ctl(opusState, OPUS_SET_VBR(0)); // CBR + } #endif qWarning("AudioInput: %d bits/s, %d hz, %d sample", iAudioQuality, iSampleRate, iFrameSize); @@ -149,8 +149,9 @@ AudioInput::~AudioInput() { wait(); #ifdef USE_OPUS - if (opusState) - opus_encoder_destroy(opusState); + if (opusState) { + oCodec->opus_encoder_destroy(opusState); + } #endif if (ceEncoder) { @@ -729,13 +730,13 @@ int AudioInput::encodeOpusFrame(short *source, int size, EncodingOutputBuffer& b int len = 0; #ifdef USE_OPUS if (bResetEncoder) { - opus_encoder_ctl(opusState, OPUS_RESET_STATE, NULL); + oCodec->opus_encoder_ctl(opusState, OPUS_RESET_STATE, NULL); bResetEncoder = false; } - opus_encoder_ctl(opusState, OPUS_SET_BITRATE(iAudioQuality)); + oCodec->opus_encoder_ctl(opusState, OPUS_SET_BITRATE(iAudioQuality)); - len = opus_encode(opusState, source, size, &buffer[0], static_cast(buffer.size())); + len = oCodec->opus_encode(opusState, source, size, &buffer[0], static_cast(buffer.size())); const int tenMsFrameCount = (size / iFrameSize); iBitrate = (len * 100 * 8) / tenMsFrameCount; #endif diff --git a/src/mumble/AudioInput.h b/src/mumble/AudioInput.h index 2fab837d6..c81c7202d 100644 --- a/src/mumble/AudioInput.h +++ b/src/mumble/AudioInput.h @@ -23,6 +23,7 @@ class AudioInput; class CELTCodec; +class OpusCodec; struct CELTEncoder; struct OpusEncoder; typedef boost::shared_ptr AudioInputPtr; @@ -72,6 +73,7 @@ class AudioInput : public QThread { inMixerFunc chooseMixer(const unsigned int nchan, SampleFormat sf, quint64 mask); void resetAudioProcessor(); + OpusCodec *oCodec; OpusEncoder *opusState; bool selectCodec(); diff --git a/src/mumble/AudioOutputSpeech.cpp b/src/mumble/AudioOutputSpeech.cpp index 1d6583a3d..259c0a991 100644 --- a/src/mumble/AudioOutputSpeech.cpp +++ b/src/mumble/AudioOutputSpeech.cpp @@ -9,14 +9,11 @@ #include "Audio.h" #include "CELTCodec.h" +#include "OpusCodec.h" #include "ClientUser.h" #include "Global.h" #include "PacketDataStream.h" -#ifdef USE_OPUS -#include "opus.h" -#endif - AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, MessageHandler::UDPMessageType type) : AudioOutputUser(user->qsName) { int err; p = user; @@ -26,6 +23,7 @@ AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, Messag cCodec = NULL; cdDecoder = NULL; dsSpeex = NULL; + oCodec = NULL; opusState = NULL; bHasTerminator = false; @@ -37,8 +35,11 @@ AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, Messag if (umtType == MessageHandler::UDPVoiceOpus) { #ifdef USE_OPUS - iAudioBufferSize *= 12; - opusState = opus_decoder_create(iSampleRate, bStereo ? 2 : 1, NULL); + oCodec = g.oCodec; + if (oCodec) { + iAudioBufferSize *= 12; + opusState = oCodec->opus_decoder_create(iSampleRate, bStereo ? 2 : 1, NULL); + } #endif } else if (umtType == MessageHandler::UDPVoiceSpeex) { speex_bits_init(&sbBits); @@ -87,7 +88,7 @@ AudioOutputSpeech::AudioOutputSpeech(ClientUser *user, unsigned int freq, Messag AudioOutputSpeech::~AudioOutputSpeech() { #ifdef USE_OPUS if (opusState) - opus_decoder_destroy(opusState); + oCodec->opus_decoder_destroy(opusState); #endif if (cdDecoder) { cCodec->celt_decoder_destroy(cdDecoder); @@ -134,8 +135,10 @@ void AudioOutputSpeech::addFrameToBuffer(const QByteArray &qbaPacket, unsigned i const unsigned char *packet = reinterpret_cast(qba.constData()); #ifdef USE_OPUS - int frames = opus_packet_get_nb_frames(packet, size); - samples = frames * opus_packet_get_samples_per_frame(packet, SAMPLE_RATE); + if (oCodec) { + int frames = oCodec->opus_packet_get_nb_frames(packet, size); + samples = frames * oCodec->opus_packet_get_samples_per_frame(packet, SAMPLE_RATE); + } #else return; #endif @@ -288,14 +291,15 @@ bool AudioOutputSpeech::needSamples(unsigned int snum) { memset(pOut, 0, sizeof(float) * iFrameSize); } else if (umtType == MessageHandler::UDPVoiceOpus) { #ifdef USE_OPUS - decodedSamples = opus_decode_float(opusState, - qba.isEmpty() ? - NULL : - reinterpret_cast(qba.constData()), - qba.size(), - pOut, - iAudioBufferSize, - 0); + decodedSamples = oCodec->opus_decode_float(opusState, + qba.isEmpty() ? + NULL : + reinterpret_cast(qba.constData()), + qba.size(), + pOut, + iAudioBufferSize, + 0); + if (decodedSamples < 0) { decodedSamples = iFrameSize; memset(pOut, 0, iFrameSize * sizeof(float)); @@ -350,7 +354,8 @@ bool AudioOutputSpeech::needSamples(unsigned int snum) { memset(pOut, 0, sizeof(float) * iFrameSize); } else if (umtType == MessageHandler::UDPVoiceOpus) { #ifdef USE_OPUS - decodedSamples = opus_decode_float(opusState, NULL, 0, pOut, iFrameSize, 0); + decodedSamples = oCodec->opus_decode_float(opusState, NULL, 0, pOut, iFrameSize, 0); + if (decodedSamples < 0) { decodedSamples = iFrameSize; memset(pOut, 0, iFrameSize * sizeof(float)); diff --git a/src/mumble/AudioOutputSpeech.h b/src/mumble/AudioOutputSpeech.h index ab41dc306..937bfb7ae 100644 --- a/src/mumble/AudioOutputSpeech.h +++ b/src/mumble/AudioOutputSpeech.h @@ -18,6 +18,7 @@ #include "Message.h" class CELTCodec; +class OpusCodec; class ClientUser; struct OpusDecoder; @@ -51,6 +52,7 @@ class AudioOutputSpeech : public AudioOutputUser { CELTCodec *cCodec; CELTDecoder *cdDecoder; + OpusCodec *oCodec; OpusDecoder *opusState; SpeexBits sbBits; diff --git a/src/mumble/Global.h b/src/mumble/Global.h index fc0be6ccc..abf78c164 100644 --- a/src/mumble/Global.h +++ b/src/mumble/Global.h @@ -29,6 +29,7 @@ class LCD; class BonjourClient; class OverlayClient; class CELTCodec; +class OpusCodec; class LogEmitter; class DeveloperConsole; @@ -74,6 +75,7 @@ public: int iAudioBandwidth; QDir qdBasePath; QMap qmCodecs; + OpusCodec *oCodec; int iCodecAlpha, iCodecBeta; bool bPreferAlpha; bool bOpus; diff --git a/src/mumble/MainWindow.cpp b/src/mumble/MainWindow.cpp index bb4b8123d..59bcb321c 100644 --- a/src/mumble/MainWindow.cpp +++ b/src/mumble/MainWindow.cpp @@ -15,6 +15,7 @@ #include "AudioWizard.h" #include "BanEditor.h" #include "CELTCodec.h" +#include "OpusCodec.h" #include "Cert.h" #include "Channel.h" #include "Connection.h" diff --git a/src/mumble/OpusCodec.cpp b/src/mumble/OpusCodec.cpp new file mode 100644 index 000000000..b819f887f --- /dev/null +++ b/src/mumble/OpusCodec.cpp @@ -0,0 +1,92 @@ +// Copyright 2005-2018 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 . + +#include "mumble_pch.hpp" + +#include "OpusCodec.h" + +#include "Audio.h" +#include "Version.h" +#include "MumbleApplication.h" + +#ifdef Q_CC_GNU +#define RESOLVE(var) { var = reinterpret_cast<__typeof__(var)>(qlOpus.resolve(#var)); bValid = bValid && (var != NULL); } +#else +#define RESOLVE(var) { * reinterpret_cast(&var) = static_cast(qlOpus.resolve(#var)); bValid = bValid && (var != NULL); } +#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("libopus0.so"); + alternatives << QString::fromLatin1("libopus.so"); + alternatives << QString::fromLatin1("opus.so"); +#else + alternatives << QString::fromLatin1("opus0.dll"); +#endif + foreach(const QString &lib, alternatives) { + qlOpus.setFileName(MumbleApplication::instance()->applicationVersionRootPath() + QLatin1String("/") + lib); + if (qlOpus.load()) { + bValid = true; + break; + } + +#ifdef Q_OS_MAC + qlOpus.setFileName(QApplication::instance()->applicationDirPath() + QLatin1String("/../Codecs/") + lib); + if (qlOpus.load()) { + bValid = true; + break; + } +#endif + +#ifdef PLUGIN_PATH + qlOpus.setFileName(QLatin1String(MUMTEXT(PLUGIN_PATH) "/") + lib); + if (qlOpus.load()) { + bValid = true; + break; + } +#endif + + qlOpus.setFileName(lib); + if (qlOpus.load()) { + bValid = true; + 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_destroy); + + RESOLVE(opus_packet_get_nb_frames); + 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())); +} diff --git a/src/mumble/OpusCodec.h b/src/mumble/OpusCodec.h new file mode 100644 index 000000000..504168cfe --- /dev/null +++ b/src/mumble/OpusCodec.h @@ -0,0 +1,46 @@ +// Copyright 2005-2018 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 . + +#ifndef MUMBLE_MUMBLE_OPUSCODEC_H_ +#define MUMBLE_MUMBLE_OPUSCODEC_H_ + +#include + +#include + +#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); + 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_packet_get_nb_frames)(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_ diff --git a/src/mumble/mumble.pro b/src/mumble/mumble.pro index cf5e6e3a2..0a25031ec 100644 --- a/src/mumble/mumble.pro +++ b/src/mumble/mumble.pro @@ -86,6 +86,7 @@ HEADERS *= BanEditor.h \ AudioOutputSpeech.h \ AudioOutputUser.h \ CELTCodec.h \ + OpusCodec.h \ CustomElements.h \ MainWindow.h \ ServerHandler.h \ @@ -156,6 +157,7 @@ SOURCES *= BanEditor.cpp \ AudioOutputUser.cpp \ main.cpp \ CELTCodec.cpp \ + OpusCodec.cpp \ CustomElements.cpp \ MainWindow.cpp \ ServerHandler.cpp \ @@ -358,7 +360,6 @@ unix:!CONFIG(bundled-opus):system(pkg-config --exists opus) { CONFIG(opus) { INCLUDEPATH *= ../../3rdparty/opus-src/celt ../../3rdparty/opus-src/include ../../3rdparty/opus-src/src ../../3rdparty/opus-build/src DEFINES *= USE_OPUS - LIBS *= -lopus unix { QMAKE_CFLAGS *= "-I../../3rdparty/opus-src/celt" "-isystem ../../3rdparty/opus-src/celt" QMAKE_CFLAGS *= "-I../../3rdparty/opus-src/include" "-isystem ../../3rdparty/opus-src/include"