From cf56bf415f990ed6facd013ae83bd2a03c3b6b0f Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Thu, 9 Jan 2020 00:18:57 +0100 Subject: [PATCH] Initial attempt at Opus compression. --- plugins/opusCodec/src/OpusCodecManager.cpp | 44 ++-- plugins/opusCodec/src/OpusCodecManager.h | 3 +- plugins/opusCodec/src/OpusCodecProvider.cpp | 8 +- plugins/opusCodec/src/OpusDecoder.cpp | 97 +++++++ plugins/opusCodec/src/OpusDecoder.h | 28 ++ plugins/opusCodec/src/OpusEncoder.cpp | 270 ++++++++++++++++++++ plugins/opusCodec/src/OpusEncoder.h | 69 +++++ 7 files changed, 486 insertions(+), 33 deletions(-) create mode 100644 plugins/opusCodec/src/OpusDecoder.cpp create mode 100644 plugins/opusCodec/src/OpusDecoder.h create mode 100644 plugins/opusCodec/src/OpusEncoder.cpp create mode 100644 plugins/opusCodec/src/OpusEncoder.h diff --git a/plugins/opusCodec/src/OpusCodecManager.cpp b/plugins/opusCodec/src/OpusCodecManager.cpp index 8f7d4fe6e5..d86208c6f2 100644 --- a/plugins/opusCodec/src/OpusCodecManager.cpp +++ b/plugins/opusCodec/src/OpusCodecManager.cpp @@ -22,6 +22,9 @@ #include #include +#include "OpusEncoder.h" +#include "OpusDecoder.h" + #define FRAME_SIZE 960 #define SAMPLE_RATE 48000 #define CHANNELS 2 @@ -31,56 +34,41 @@ #define MAX_FRAME_SIZE 6*FRAME_SIZE #define MAX_PACKET_SIZE 3*1276 -const char* OpusCodec::NAME { "opus" }; +const char* AthenaOpusCodec::NAME { "opus" }; -void OpusCodec::init() { +void AthenaOpusCodec::init() { } -void OpusCodec::deinit() { +void AthenaOpusCodec::deinit() { } -bool OpusCodec::activate() { +bool AthenaOpusCodec::activate() { CodecPlugin::activate(); return true; } -void OpusCodec::deactivate() { +void AthenaOpusCodec::deactivate() { CodecPlugin::deactivate(); } -bool OpusCodec::isSupported() const { +bool AthenaOpusCodec::isSupported() const { return true; } -class OpusEncoder : public Encoder { -public: - OpusEncoder(int sampleRate, int numChannels) { - - } - - virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override { - encodedBuffer.resize(_encodedSize); - } - -private: - int _encodedSize; -}; - - -Encoder* OpusCodec::createEncoder(int sampleRate, int numChannels) { - return new OpusEncoder(sampleRate, numChannels); +Encoder* AthenaOpusCodec::createEncoder(int sampleRate, int numChannels) { + return new AthenaOpusEncoder(sampleRate, numChannels); } -Decoder* OpusCodec::createDecoder(int sampleRate, int numChannels) { - return this; +Decoder* AthenaOpusCodec::createDecoder(int sampleRate, int numChannels) { + return new AthenaOpusDecoder(sampleRate, numChannels); } -void OpusCodec::releaseEncoder(Encoder* encoder) { +void AthenaOpusCodec::releaseEncoder(Encoder* encoder) { delete encoder; } -void OpusCodec::releaseDecoder(Decoder* decoder) { +void AthenaOpusCodec::releaseDecoder(Decoder* decoder) { delete decoder; -} \ No newline at end of file +} diff --git a/plugins/opusCodec/src/OpusCodecManager.h b/plugins/opusCodec/src/OpusCodecManager.h index b75e78b23c..1db1731fc0 100644 --- a/plugins/opusCodec/src/OpusCodecManager.h +++ b/plugins/opusCodec/src/OpusCodecManager.h @@ -14,7 +14,7 @@ #include -class OpusCodec : public CodecPlugin, public Encoder, public Decoder { +class AthenaOpusCodec : public CodecPlugin { Q_OBJECT public: @@ -35,6 +35,7 @@ public: virtual void releaseEncoder(Encoder* encoder) override; virtual void releaseDecoder(Decoder* decoder) override; + private: static const char* NAME; }; diff --git a/plugins/opusCodec/src/OpusCodecProvider.cpp b/plugins/opusCodec/src/OpusCodecProvider.cpp index ab72f25023..79f01de4bd 100644 --- a/plugins/opusCodec/src/OpusCodecProvider.cpp +++ b/plugins/opusCodec/src/OpusCodecProvider.cpp @@ -17,20 +17,20 @@ #include "OpusCodecManager.h" -class OpusCodecProvider : public QObject, public CodecProvider { +class AthenaOpusCodecProvider : public QObject, public CodecProvider { Q_OBJECT Q_PLUGIN_METADATA(IID CodecProvider_iid FILE "plugin.json") Q_INTERFACES(CodecProvider) public: - OpusCodecProvider(QObject* parent = nullptr) : QObject(parent) {} - virtual ~OpusCodecProvider() {} + AthenaOpusCodecProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~AthenaOpusCodecProvider() {} virtual CodecPluginList getCodecPlugins() override { static std::once_flag once; std::call_once(once, [&] { - CodecPluginPointer opusCodec(new OpusCodec()); + CodecPluginPointer opusCodec(new AthenaOpusCodec()); if (opusCodec->isSupported()) { _codecPlugins.push_back(opusCodec); } diff --git a/plugins/opusCodec/src/OpusDecoder.cpp b/plugins/opusCodec/src/OpusDecoder.cpp new file mode 100644 index 0000000000..d47274e523 --- /dev/null +++ b/plugins/opusCodec/src/OpusDecoder.cpp @@ -0,0 +1,97 @@ +#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; + + _opus_sample_rate = sampleRate; + _opus_num_channels = 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"; +} + +AthenaOpusDecoder::~AthenaOpusDecoder() +{ + if ( _decoder ) + opus_decoder_destroy(_decoder); + +} + +void AthenaOpusDecoder::decode(const QByteArray &encodedBuffer, QByteArray &decodedBuffer) +{ + assert(_decoder); + + PerformanceTimer perfTimer("AthenaOpusDecoder::decode"); + + decodedBuffer.resize( 65536 ); // Brute force, yeah! + int frame_size = decodedBuffer.size() / _opus_num_channels / static_cast(sizeof( opus_int16 )); + + int frames = opus_decode( _decoder, reinterpret_cast(encodedBuffer.data()), encodedBuffer.length(), reinterpret_cast(decodedBuffer.data()), frame_size, 1 ); + + if ( frames >= 0 ) { + qCDebug(decoder) << "Decoded " << frames << " Opus frames"; + decodedBuffer.resize( frames * static_cast(sizeof( opus_int16 )) * _opus_num_channels ); + } else { + qCCritical(decoder) << "Failed to decode audio: " << error_to_string(frames); + } + +} + +void AthenaOpusDecoder::lostFrame(QByteArray &decodedBuffer) +{ + assert(_decoder); + + PerformanceTimer perfTimer("AthenaOpusDecoder::lostFrame"); + + decodedBuffer.resize( 65536 ); // Brute force, yeah! + int frame_size = decodedBuffer.size() / _opus_num_channels / static_cast(sizeof( opus_int16 )); + + int frames = opus_decode( _decoder, nullptr, 0, reinterpret_cast(decodedBuffer.data()), frame_size, 1 ); + + if ( frames >= 0 ) { + qCDebug(decoder) << "Produced " << frames << " opus frames from a lost frame"; + decodedBuffer.resize( frames * static_cast(sizeof( opus_int16 )) * _opus_num_channels ); + } else { + qCCritical(decoder) << "Failed to decode lost frame: " << error_to_string(frames); + } +} + + diff --git a/plugins/opusCodec/src/OpusDecoder.h b/plugins/opusCodec/src/OpusDecoder.h new file mode 100644 index 0000000000..d6b869695e --- /dev/null +++ b/plugins/opusCodec/src/OpusDecoder.h @@ -0,0 +1,28 @@ +#ifndef OPUSDECODER_H +#define OPUSDECODER_H + + +#include +#include "opus/opus.h" + + +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 _opus_sample_rate = 0; + int _opus_num_channels = 0; +}; + + +#endif // OPUSDECODER_H diff --git a/plugins/opusCodec/src/OpusEncoder.cpp b/plugins/opusCodec/src/OpusEncoder.cpp new file mode 100644 index 0000000000..a3a7ed62bc --- /dev/null +++ b/plugins/opusCodec/src/OpusEncoder.cpp @@ -0,0 +1,270 @@ + +#include +#include + +#include "OpusEncoder.h" +#include "OpusWrapper.h" +#include "opus/opus.h" + +static QLoggingCategory encoder("AthenaOpusEncoder"); + +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); + } +} + + + +AthenaOpusEncoder::AthenaOpusEncoder(int sampleRate, int numChannels) +{ + _opus_sample_rate = sampleRate; + _opus_channels = numChannels; + + int error; + + _encoder = opus_encoder_create(sampleRate, numChannels, DEFAULT_APPLICATION, &error); + + if ( error != OPUS_OK ) { + qCCritical(encoder) << "Failed to initialize Opus encoder: " << error_to_string(error); + _encoder = nullptr; + return; + } + + setBitrate(DEFAULT_BITRATE); + setComplexity(DEFAULT_COMPLEXITY); + setApplication(DEFAULT_APPLICATION); + setSignal(DEFAULT_SIGNAL); + + qCDebug(encoder) << "Opus encoder initialized"; +} + +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 frame_size = decodedBuffer.length()/ _opus_channels / static_cast(sizeof(opus_int16)); + + int bytes = opus_encode(_encoder, reinterpret_cast(decodedBuffer.data()), frame_size, reinterpret_cast(encodedBuffer.data()), encodedBuffer.size() ); + + if ( bytes >= 0 ) { + qCDebug(encoder) << "Encoded " << decodedBuffer.length() << " bytes into " << bytes << " opus bytes"; + encodedBuffer.resize(bytes); + } else { + encodedBuffer.resize(0); + + qCWarning(encoder) << "Error when encoding " << decodedBuffer.length() << " bytes of audio: " << error_to_string(bytes); + } + +} + +int AthenaOpusEncoder::getComplexity() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_COMPLEXITY(&ret)); + return ret; +} + +void AthenaOpusEncoder::setComplexity(int complexity) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_COMPLEXITY(complexity)); + + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting complexity to " << complexity << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getBitrate() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_BITRATE(&ret)); + return ret; +} + +void AthenaOpusEncoder::setBitrate(int bitrate) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting bitrate to " << bitrate << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getVBR() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_VBR(&ret)); + return ret; +} + +void AthenaOpusEncoder::setVBR(int vbr) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_VBR(vbr)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting VBR to " << vbr << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getVBRConstraint() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_VBR_CONSTRAINT(&ret)); + return ret; +} + +void AthenaOpusEncoder::setVBRConstraint(int vbr_const) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_VBR_CONSTRAINT(vbr_const)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting VBR constraint to " << vbr_const << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getMaxBandwidth() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_MAX_BANDWIDTH(&ret)); + return ret; +} + +void AthenaOpusEncoder::setMaxBandwidth(int maxbw) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_MAX_BANDWIDTH(maxbw)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting max bandwidth to " << maxbw << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getBandwidth() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_BANDWIDTH(&ret)); + return ret; +} + +void AthenaOpusEncoder::setBandwidth(int bw) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_BANDWIDTH(bw)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting bandwidth to " << bw << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getSignal() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_SIGNAL(&ret)); + return ret; +} + +void AthenaOpusEncoder::setSignal(int signal) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_SIGNAL(signal)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting signal to " << signal << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getApplication() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_APPLICATION(&ret)); + return ret; +} + +void AthenaOpusEncoder::setApplication(int app) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_APPLICATION(app)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting application to " << app << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getLookahead() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_LOOKAHEAD(&ret)); + return ret; +} + +int AthenaOpusEncoder::getInbandFEC() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_INBAND_FEC(&ret)); + return ret; +} + +void AthenaOpusEncoder::setInbandFEC(int fec) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_INBAND_FEC(fec)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting inband FEC to " << fec << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getExpectedPacketLossPercent() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_PACKET_LOSS_PERC(&ret)); + return ret; +} + +void AthenaOpusEncoder::setExpectedPacketLossPercent(int perc) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_PACKET_LOSS_PERC(perc)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting loss percent to " << perc << ": " << error_to_string(ret); +} + +int AthenaOpusEncoder::getDTX() const +{ + assert(_encoder); + int ret; + opus_encoder_ctl(_encoder, OPUS_GET_DTX(&ret)); + return ret; +} + +void AthenaOpusEncoder::setDTX(int dtx) +{ + assert(_encoder); + int ret = opus_encoder_ctl(_encoder, OPUS_SET_DTX(dtx)); + if ( ret != OPUS_OK ) + qCWarning(encoder) << "Error when setting DTX to " << dtx << ": " << error_to_string(ret); +} + + diff --git a/plugins/opusCodec/src/OpusEncoder.h b/plugins/opusCodec/src/OpusEncoder.h new file mode 100644 index 0000000000..859178281b --- /dev/null +++ b/plugins/opusCodec/src/OpusEncoder.h @@ -0,0 +1,69 @@ +#ifndef OPUSENCODER_H +#define OPUSENCODER_H +#include +#include "OpusWrapper.h" +#include "opus/opus.h" + + +class AthenaOpusEncoder : public Encoder { +public: + const int DEFAULT_BITRATE = 44100; + const int DEFAULT_COMPLEXITY = 10; + const int DEFAULT_APPLICATION = OPUS_APPLICATION_VOIP; + const int DEFAULT_SIGNAL = OPUS_AUTO; + + + 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 vbr_const); + + int getMaxBandwidth() const; + void setMaxBandwidth(int maxbw); + + int getBandwidth() const; + void setBandwidth(int bw); + + int getSignal() const; + void setSignal(int signal); + + int getApplication() const; + void setApplication(int app); + + int getLookahead() const; + + int getInbandFEC() const; + void setInbandFEC(int fec); + + int getExpectedPacketLossPercent() const; + void setExpectedPacketLossPercent(int perc); + + int getDTX() const; + void setDTX(int dtx); + + +private: + + int _opus_sample_rate = 0; + int _opus_channels = 0; + int _opus_expected_loss = 0; + + + OpusEncoder *_encoder = nullptr; +}; + + +#endif // OPUSENCODER_H