diff --git a/cmake/macros/TargetOpus.cmake b/cmake/macros/TargetOpus.cmake new file mode 100644 index 0000000000..a8faf5139e --- /dev/null +++ b/cmake/macros/TargetOpus.cmake @@ -0,0 +1,13 @@ +# +# Created by Michael Bailey on 12/20/2019 +# Copyright 2019 Michael Bailey +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_opus) + find_library(OPUS_LIBRARY_RELEASE NAMES opus PATHS ${VCPKG_INSTALL_ROOT}/lib) + find_library(OPUS_LIBRARY_DEBUG NAMES opus PATHS ${VCPKG_INSTALL_ROOT}/debug/lib) + select_library_configurations(OPUS) + target_link_libraries(${TARGET_NAME} ${OPUS_LIBRARY}) +endmacro() diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL index 4cf952ccf0..b1a7f96a00 100644 --- a/cmake/ports/hifi-deps/CONTROL +++ b/cmake/ports/hifi-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-deps -Version: 0.3 +Version: 0.4 Description: Collected dependencies for High Fidelity applications -Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib, webrtc (!android) +Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), opus, tbb (!android&!osx), zlib, webrtc (!android) diff --git a/cmake/ports/opus/CONTROL b/cmake/ports/opus/CONTROL new file mode 100644 index 0000000000..4314774ab5 --- /dev/null +++ b/cmake/ports/opus/CONTROL @@ -0,0 +1,3 @@ +Source: opus +Version: 1.3.1 +Description: Totally open, royalty-free, highly versatile audio codec diff --git a/cmake/ports/opus/portfile.cmake b/cmake/ports/opus/portfile.cmake new file mode 100644 index 0000000000..bf23718df8 --- /dev/null +++ b/cmake/ports/opus/portfile.cmake @@ -0,0 +1,28 @@ +include(vcpkg_common_functions) + +vcpkg_from_github( + OUT_SOURCE_PATH + SOURCE_PATH + REPO + xiph/opus + REF + e85ed7726db5d677c9c0677298ea0cb9c65bdd23 + SHA512 + a8c7e5bf383c06f1fdffd44d9b5f658f31eb4800cb59d12da95ddaeb5646f7a7b03025f4663362b888b1374d4cc69154f006ba07b5840ec61ddc1a1af01d6c54 + HEAD_REF + master) + +vcpkg_configure_cmake(SOURCE_PATH ${SOURCE_PATH} PREFER_NINJA) +vcpkg_install_cmake() +vcpkg_fixup_cmake_targets(CONFIG_PATH lib/cmake/Opus) +vcpkg_copy_pdbs() + +file(INSTALL + ${SOURCE_PATH}/COPYING + DESTINATION + ${CURRENT_PACKAGES_DIR}/share/opus + RENAME copyright) + +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/cmake + ${CURRENT_PACKAGES_DIR}/lib/cmake + ${CURRENT_PACKAGES_DIR}/debug/include) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b854955953..cdf92918c6 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1227,8 +1227,8 @@ "name": "codec_preference_order", "label": "Audio Codec Preference Order", "help": "List of codec names in order of preferred usage", - "placeholder": "hifiAC, zlib, pcm", - "default": "hifiAC,zlib,pcm", + "placeholder": "opus, hifiAC, zlib, pcm", + "default": "opus,hifiAC,zlib,pcm", "advanced": true } ] diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c9bc3e4c33..c88bb8a00d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -43,6 +43,8 @@ set(DIR "pcmCodec") add_subdirectory(${DIR}) set(DIR "hifiCodec") add_subdirectory(${DIR}) +set(DIR "opusCodec") +add_subdirectory(${DIR}) # example plugins set(DIR "KasenAPIExample") diff --git a/plugins/hifiCodec/CMakeLists.txt b/plugins/hifiCodec/CMakeLists.txt index 0d4f093fc2..5515602d2f 100644 --- a/plugins/hifiCodec/CMakeLists.txt +++ b/plugins/hifiCodec/CMakeLists.txt @@ -8,7 +8,7 @@ set(TARGET_NAME hifiCodec) setup_hifi_client_server_plugin() -link_hifi_libraries(audio plugins) +link_hifi_libraries(shared audio plugins) target_hifiAudioCodec() if (BUILD_SERVER) install_beside_console() diff --git a/plugins/hifiCodec/src/HiFiCodec.cpp b/plugins/hifiCodec/src/HiFiCodec.cpp index 99bb411539..a93e6c47d0 100644 --- a/plugins/hifiCodec/src/HiFiCodec.cpp +++ b/plugins/hifiCodec/src/HiFiCodec.cpp @@ -13,6 +13,7 @@ #include #include +#include const char* HiFiCodec::NAME { "hifiAC" }; @@ -44,6 +45,8 @@ public: } virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override { + PerformanceTimer perfTimer("HiFiEncoder::encode"); + encodedBuffer.resize(_encodedSize); AudioEncoder::process((const int16_t*)decodedBuffer.constData(), (int16_t*)encodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); } @@ -58,11 +61,15 @@ public: } virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override { + PerformanceTimer perfTimer("HiFiEncoder::decode"); + decodedBuffer.resize(_decodedSize); AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, true); } virtual void lostFrame(QByteArray& decodedBuffer) override { + PerformanceTimer perfTimer("HiFiEncoder::lostFrame"); + decodedBuffer.resize(_decodedSize); // this performs packet loss interpolation AudioDecoder::process(nullptr, (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, false); diff --git a/plugins/opusCodec/CMakeLists.txt b/plugins/opusCodec/CMakeLists.txt new file mode 100644 index 0000000000..583aff85d6 --- /dev/null +++ b/plugins/opusCodec/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Created by Michael Bailey on 12/20/2019 +# Copyright 2019 Michael Bailey +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# + +set(TARGET_NAME opusCodec) +setup_hifi_client_server_plugin() +link_hifi_libraries(shared audio plugins) +target_opus() + +if (BUILD_SERVER) + install_beside_console() +endif () diff --git a/plugins/opusCodec/src/OpusCodecManager.cpp b/plugins/opusCodec/src/OpusCodecManager.cpp new file mode 100644 index 0000000000..1e3d73a229 --- /dev/null +++ b/plugins/opusCodec/src/OpusCodecManager.cpp @@ -0,0 +1,58 @@ +// +// opusCodec.cpp +// plugins/opusCodec/src +// +// Created by Michael Bailey on 12/20/2019 +// Copyright 2019 Michael Bailey +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OpusCodecManager.h" + +#include + +#include + +#include "OpusEncoder.h" +#include "OpusDecoder.h" + +const char* AthenaOpusCodec::NAME { "opus" }; + +void AthenaOpusCodec::init() { +} + +void AthenaOpusCodec::deinit() { +} + +bool AthenaOpusCodec::activate() { + CodecPlugin::activate(); + return true; +} + +void AthenaOpusCodec::deactivate() { + CodecPlugin::deactivate(); +} + + +bool AthenaOpusCodec::isSupported() const { + return true; +} + + +Encoder* AthenaOpusCodec::createEncoder(int sampleRate, int numChannels) { + return new AthenaOpusEncoder(sampleRate, numChannels); +} + +Decoder* AthenaOpusCodec::createDecoder(int sampleRate, int numChannels) { + return new AthenaOpusDecoder(sampleRate, numChannels); +} + +void AthenaOpusCodec::releaseEncoder(Encoder* encoder) { + delete encoder; +} + +void AthenaOpusCodec::releaseDecoder(Decoder* decoder) { + delete decoder; +} diff --git a/plugins/opusCodec/src/OpusCodecManager.h b/plugins/opusCodec/src/OpusCodecManager.h new file mode 100644 index 0000000000..be6a6b2ff0 --- /dev/null +++ b/plugins/opusCodec/src/OpusCodecManager.h @@ -0,0 +1,42 @@ +// +// OpusCodecManager.h +// plugins/opusCodec/src +// +// Created by Michael Bailey on 12/20/2019 +// Copyright 2019 Michael Bailey +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi__OpusCodecManager_h +#define hifi__OpusCodecManager_h + +#include + +class AthenaOpusCodec : public CodecPlugin { + Q_OBJECT + +public: + // Plugin functions + bool isSupported() const override; + const QString getName() const override { return NAME; } + + void init() override; + void deinit() override; + + /// Called when a plugin is being activated for use. May be called multiple times. + bool activate() override; + /// Called when a plugin is no longer being used. May be called multiple times. + void deactivate() override; + + virtual Encoder* createEncoder(int sampleRate, int numChannels) override; + virtual Decoder* createDecoder(int sampleRate, int numChannels) override; + virtual void releaseEncoder(Encoder* encoder) override; + virtual void releaseDecoder(Decoder* decoder) override; + +private: + static const char* NAME; +}; + +#endif // hifi__opusCodecManager_h diff --git a/plugins/opusCodec/src/OpusCodecProvider.cpp b/plugins/opusCodec/src/OpusCodecProvider.cpp new file mode 100644 index 0000000000..79f01de4bd --- /dev/null +++ b/plugins/opusCodec/src/OpusCodecProvider.cpp @@ -0,0 +1,45 @@ +// +// Created by Michael Bailey on 12/20/2019 +// Copyright 2019 Michael Bailey +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include + +#include +#include + +#include "OpusCodecManager.h" + +class AthenaOpusCodecProvider : public QObject, public CodecProvider { + Q_OBJECT + Q_PLUGIN_METADATA(IID CodecProvider_iid FILE "plugin.json") + Q_INTERFACES(CodecProvider) + +public: + AthenaOpusCodecProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~AthenaOpusCodecProvider() {} + + virtual CodecPluginList getCodecPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + + CodecPluginPointer opusCodec(new AthenaOpusCodec()); + if (opusCodec->isSupported()) { + _codecPlugins.push_back(opusCodec); + } + }); + return _codecPlugins; + } + +private: + CodecPluginList _codecPlugins; +}; + +#include "OpusCodecProvider.moc" diff --git a/plugins/opusCodec/src/OpusDecoder.cpp b/plugins/opusCodec/src/OpusDecoder.cpp new file mode 100644 index 0000000000..e3e4e3645a --- /dev/null +++ b/plugins/opusCodec/src/OpusDecoder.cpp @@ -0,0 +1,131 @@ +// +// OpusCodecManager.h +// plugins/opusCodec/src +// +// Copyright 2020 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include "OpusDecoder.h" + +static QLoggingCategory decoder("AthenaOpusDecoder"); + +static QString error_to_string(int error) { + switch (error) { + case OPUS_OK: + return "OK"; + case OPUS_BAD_ARG: + return "One or more invalid/out of range arguments."; + case OPUS_BUFFER_TOO_SMALL: + return "The mode struct passed is invalid."; + case OPUS_INTERNAL_ERROR: + return "An internal error was detected."; + case OPUS_INVALID_PACKET: + return "The compressed data passed is corrupted."; + case OPUS_UNIMPLEMENTED: + return "Invalid/unsupported request number."; + case OPUS_INVALID_STATE: + return "An encoder or decoder structure is invalid or already freed."; + default: + return QString("Unknown error code: %i").arg(error); + } +} + + +AthenaOpusDecoder::AthenaOpusDecoder(int sampleRate, int numChannels) { + int error; + + _opusSampleRate = sampleRate; + _opusNumChannels = numChannels; + + _decoder = opus_decoder_create(sampleRate, numChannels, &error); + + if (error != OPUS_OK) { + qCCritical(decoder) << "Failed to initialize Opus encoder: " << error_to_string(error); + _decoder = nullptr; + return; + } + + + qCDebug(decoder) << "Opus decoder initialized, sampleRate = " << sampleRate << "; numChannels = " << numChannels; +} + +AthenaOpusDecoder::~AthenaOpusDecoder() { + if (_decoder) { + opus_decoder_destroy(_decoder); + } + +} + +void AthenaOpusDecoder::decode(const QByteArray &encodedBuffer, QByteArray &decodedBuffer) { + assert(_decoder); + PerformanceTimer perfTimer("AthenaOpusDecoder::decode"); + + // The audio system encodes and decodes always in fixed size chunks + int bufferSize = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * static_cast(sizeof(int16_t)) + * _opusNumChannels; + + decodedBuffer.resize(bufferSize); + int bufferFrames = decodedBuffer.size() / _opusNumChannels / static_cast(sizeof(opus_int16)); + int decoded_frames = opus_decode(_decoder, reinterpret_cast(encodedBuffer.data()), + encodedBuffer.length(), reinterpret_cast(decodedBuffer.data()), bufferFrames, 0); + + if (decoded_frames >= 0) { + + if (decoded_frames < bufferFrames) { + qCWarning(decoder) << "Opus decoder returned " << decoded_frames << ", but " << bufferFrames + << " were expected!"; + + int start = decoded_frames * static_cast(sizeof(int16_t)) * _opusNumChannels; + memset( &decodedBuffer.data()[start], 0, static_cast(decodedBuffer.length() - start)); + } else if (decoded_frames > bufferFrames) { + // This should never happen + qCCritical(decoder) << "Opus decoder returned " << decoded_frames << ", but only " << bufferFrames + << " were expected! Buffer overflow!?"; + } + } else { + qCCritical(decoder) << "Failed to decode audio: " << error_to_string(decoded_frames); + decodedBuffer.fill('\0'); + } + +} + +void AthenaOpusDecoder::lostFrame(QByteArray &decodedBuffer) { + assert(_decoder); + + PerformanceTimer perfTimer("AthenaOpusDecoder::lostFrame"); + + int bufferSize = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * static_cast(sizeof(int16_t)) + * _opusNumChannels; + decodedBuffer.resize(bufferSize); + int bufferFrames = decodedBuffer.size() / _opusNumChannels / static_cast(sizeof(opus_int16)); + + int decoded_frames = opus_decode(_decoder, nullptr, 0, reinterpret_cast(decodedBuffer.data()), + bufferFrames, 1); + + if (decoded_frames >= 0) { + + if ( decoded_frames < bufferFrames ) { + qCWarning(decoder) << "Opus decoder returned " << decoded_frames << ", but " << bufferFrames + << " were expected!"; + + int start = decoded_frames * static_cast(sizeof(int16_t)) * _opusNumChannels; + memset( &decodedBuffer.data()[start], 0, static_cast(decodedBuffer.length() - start)); + } else if (decoded_frames > bufferFrames) { + // This should never happen + qCCritical(decoder) << "Opus decoder returned " << decoded_frames << ", but only " << bufferFrames + << " were expected! Buffer overflow!?"; + } + + } else { + qCCritical(decoder) << "Failed to decode lost frame: " << error_to_string(decoded_frames); + decodedBuffer.fill('\0'); + } + +} diff --git a/plugins/opusCodec/src/OpusDecoder.h b/plugins/opusCodec/src/OpusDecoder.h new file mode 100644 index 0000000000..095893856b --- /dev/null +++ b/plugins/opusCodec/src/OpusDecoder.h @@ -0,0 +1,39 @@ +// +// OpusCodecManager.h +// plugins/opusCodec/src +// +// Copyright 2020 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef OPUSDECODER_H +#define OPUSDECODER_H + + +#include +#include + + +class AthenaOpusDecoder : public Decoder { +public: + AthenaOpusDecoder(int sampleRate, int numChannels); + ~AthenaOpusDecoder() override; + + + virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override; + virtual void lostFrame(QByteArray &decodedBuffer) override; + + +private: + int _encodedSize; + + OpusDecoder* _decoder = nullptr; + int _opusSampleRate = 0; + int _opusNumChannels = 0; + int _decodedSize = 0; +}; + + +#endif // OPUSDECODER_H diff --git a/plugins/opusCodec/src/OpusEncoder.cpp b/plugins/opusCodec/src/OpusEncoder.cpp new file mode 100644 index 0000000000..3408701633 --- /dev/null +++ b/plugins/opusCodec/src/OpusEncoder.cpp @@ -0,0 +1,273 @@ +// +// OpusCodecManager.h +// plugins/opusCodec/src +// +// Copyright 2020 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include "OpusEncoder.h" + +static QLoggingCategory encoder("AthenaOpusEncoder"); + +static QString errorToString(int error) { + switch (error) { + case OPUS_OK: + return "OK"; + case OPUS_BAD_ARG: + return "One or more invalid/out of range arguments."; + case OPUS_BUFFER_TOO_SMALL: + return "The mode struct passed is invalid."; + case OPUS_INTERNAL_ERROR: + return "An internal error was detected."; + case OPUS_INVALID_PACKET: + return "The compressed data passed is corrupted."; + case OPUS_UNIMPLEMENTED: + return "Invalid/unsupported request number."; + case OPUS_INVALID_STATE: + return "An encoder or decoder structure is invalid or already freed."; + default: + return QString("Unknown error code: %i").arg(error); + } +} + + + +AthenaOpusEncoder::AthenaOpusEncoder(int sampleRate, int numChannels) { + _opusSampleRate = sampleRate; + _opusChannels = numChannels; + + int error; + + _encoder = opus_encoder_create(sampleRate, numChannels, DEFAULT_APPLICATION, &error); + + if (error != OPUS_OK) { + qCCritical(encoder) << "Failed to initialize Opus encoder: " << errorToString(error); + _encoder = nullptr; + return; + } + + setBitrate(DEFAULT_BITRATE); + setComplexity(DEFAULT_COMPLEXITY); + setApplication(DEFAULT_APPLICATION); + setSignal(DEFAULT_SIGNAL); + + qCDebug(encoder) << "Opus encoder initialized, sampleRate = " << sampleRate << "; numChannels = " << numChannels; +} + +AthenaOpusEncoder::~AthenaOpusEncoder() { + opus_encoder_destroy(_encoder); +} + + + +void AthenaOpusEncoder::encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) { + + PerformanceTimer perfTimer("AthenaOpusEncoder::encode"); + assert(_encoder); + + encodedBuffer.resize(decodedBuffer.size()); + int frameSize = decodedBuffer.length() / _opusChannels / static_cast(sizeof(opus_int16)); + + int bytes = opus_encode(_encoder, reinterpret_cast(decodedBuffer.constData()), frameSize, + reinterpret_cast(encodedBuffer.data()), encodedBuffer.size() ); + + if (bytes >= 0) { + encodedBuffer.resize(bytes); + } else { + encodedBuffer.resize(0); + + qCWarning(encoder) << "Error when encoding " << decodedBuffer.length() << " bytes of audio: " + << errorToString(bytes); + } + +} + +int AthenaOpusEncoder::getComplexity() const { + assert(_encoder); + int returnValue; + opus_encoder_ctl(_encoder, OPUS_GET_COMPLEXITY(&returnValue)); + return returnValue; +} + +void AthenaOpusEncoder::setComplexity(int complexity) { + assert(_encoder); + int returnValue = opus_encoder_ctl(_encoder, OPUS_SET_COMPLEXITY(complexity)); + + if (returnValue != OPUS_OK) { + qCWarning(encoder) << "Error when setting complexity to " << complexity << ": " << errorToString(returnValue); + } +} + +int AthenaOpusEncoder::getBitrate() const { + assert(_encoder); + int returnValue; + opus_encoder_ctl(_encoder, OPUS_GET_BITRATE(&returnValue)); + return returnValue; +} + +void AthenaOpusEncoder::setBitrate(int bitrate) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting bitrate to " << bitrate << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getVBR() const { + assert(_encoder); + int returnValue; + opus_encoder_ctl(_encoder, OPUS_GET_VBR(&returnValue)); + return returnValue; +} + +void AthenaOpusEncoder::setVBR(int vbr) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_VBR(vbr)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting VBR to " << vbr << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getVBRConstraint() const { + assert(_encoder); + int returnValue; + opus_encoder_ctl(_encoder, OPUS_GET_VBR_CONSTRAINT(&returnValue)); + return returnValue; +} + +void AthenaOpusEncoder::setVBRConstraint(int vbr_const) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_VBR_CONSTRAINT(vbr_const)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting VBR constraint to " << vbr_const << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getMaxBandwidth() const { + assert(_encoder); + int returnValue; + opus_encoder_ctl(_encoder, OPUS_GET_MAX_BANDWIDTH(&returnValue)); + return returnValue; +} + +void AthenaOpusEncoder::setMaxBandwidth(int maxBandwidth) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_MAX_BANDWIDTH(maxBandwidth)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting max bandwidth to " << maxBandwidth << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getBandwidth() const { + assert(_encoder); + int bandwidth; + opus_encoder_ctl(_encoder, OPUS_GET_BANDWIDTH(&bandwidth)); + return bandwidth; +} + +void AthenaOpusEncoder::setBandwidth(int bandwidth) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_BANDWIDTH(bandwidth)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting bandwidth to " << bandwidth << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getSignal() const { + assert(_encoder); + int signal; + opus_encoder_ctl(_encoder, OPUS_GET_SIGNAL(&signal)); + return signal; +} + +void AthenaOpusEncoder::setSignal(int signal) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_SIGNAL(signal)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting signal to " << signal << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getApplication() const { + assert(_encoder); + int applicationValue; + opus_encoder_ctl(_encoder, OPUS_GET_APPLICATION(&applicationValue)); + return applicationValue; +} + +void AthenaOpusEncoder::setApplication(int application) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_APPLICATION(application)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting application to " << application << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getLookahead() const { + assert(_encoder); + int lookAhead; + opus_encoder_ctl(_encoder, OPUS_GET_LOOKAHEAD(&lookAhead)); + return lookAhead; +} + +int AthenaOpusEncoder::getInbandFEC() const { + assert(_encoder); + int fec; + opus_encoder_ctl(_encoder, OPUS_GET_INBAND_FEC(&fec)); + return fec; +} + +void AthenaOpusEncoder::setInbandFEC(int inBandFEC) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_INBAND_FEC(inBandFEC)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting inband FEC to " << inBandFEC << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getExpectedPacketLossPercentage() const { + assert(_encoder); + int lossPercentage; + opus_encoder_ctl(_encoder, OPUS_GET_PACKET_LOSS_PERC(&lossPercentage)); + return lossPercentage; +} + +void AthenaOpusEncoder::setExpectedPacketLossPercentage(int percentage) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_PACKET_LOSS_PERC(percentage)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting loss percent to " << percentage << ": " << errorToString(errorCode); + } +} + +int AthenaOpusEncoder::getDTX() const { + assert(_encoder); + int dtx; + opus_encoder_ctl(_encoder, OPUS_GET_DTX(&dtx)); + return dtx; +} + +void AthenaOpusEncoder::setDTX(int dtx) { + assert(_encoder); + int errorCode = opus_encoder_ctl(_encoder, OPUS_SET_DTX(dtx)); + + if (errorCode != OPUS_OK) { + qCWarning(encoder) << "Error when setting DTX to " << dtx << ": " << errorToString(errorCode); + } +} diff --git a/plugins/opusCodec/src/OpusEncoder.h b/plugins/opusCodec/src/OpusEncoder.h new file mode 100644 index 0000000000..10640bf409 --- /dev/null +++ b/plugins/opusCodec/src/OpusEncoder.h @@ -0,0 +1,78 @@ +// +// OpusCodecManager.h +// plugins/opusCodec/src +// +// Copyright 2020 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef OPUSENCODER_H +#define OPUSENCODER_H +#include +#include + + +class AthenaOpusEncoder : public Encoder { +public: + + AthenaOpusEncoder(int sampleRate, int numChannels); + ~AthenaOpusEncoder() override; + + virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override; + + + int getComplexity() const; + void setComplexity(int complexity); + + int getBitrate() const; + void setBitrate(int bitrate); + + int getVBR() const; + void setVBR(int vbr); + + int getVBRConstraint() const; + void setVBRConstraint(int vbrConstraint); + + int getMaxBandwidth() const; + void setMaxBandwidth(int maxBandwidth); + + int getBandwidth() const; + void setBandwidth(int bandwidth); + + int getSignal() const; + void setSignal(int signal); + + int getApplication() const; + void setApplication(int application); + + int getLookahead() const; + + int getInbandFEC() const; + void setInbandFEC(int inBandFEC); + + int getExpectedPacketLossPercentage() const; + void setExpectedPacketLossPercentage(int percentage); + + int getDTX() const; + void setDTX(int dtx); + + +private: + + const int DEFAULT_BITRATE = 128000; + const int DEFAULT_COMPLEXITY = 10; + const int DEFAULT_APPLICATION = OPUS_APPLICATION_VOIP; + const int DEFAULT_SIGNAL = OPUS_AUTO; + + int _opusSampleRate = 0; + int _opusChannels = 0; + int _opusExpectedLoss = 0; + + + OpusEncoder* _encoder = nullptr; +}; + + +#endif // OPUSENCODER_H diff --git a/plugins/opusCodec/src/plugin.json b/plugins/opusCodec/src/plugin.json new file mode 100644 index 0000000000..17217d6017 --- /dev/null +++ b/plugins/opusCodec/src/plugin.json @@ -0,0 +1,4 @@ +{ + "name": "Opus Codec", + "version": 1 +}