Initial attempt at Opus compression.

This commit is contained in:
Dale Glass 2020-01-09 00:18:57 +01:00
parent a39ad97a1f
commit cf56bf415f
7 changed files with 486 additions and 33 deletions

View file

@ -22,6 +22,9 @@
#include <opus/opus_multistream.h>
#include <opus/opus_projection.h>
#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;
}
}

View file

@ -14,7 +14,7 @@
#include <plugins/CodecPlugin.h>
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;
};

View file

@ -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);
}

View file

@ -0,0 +1,97 @@
#include <PerfStat.h>
#include <QtCore/QLoggingCategory>
#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<int>(sizeof( opus_int16 ));
int frames = opus_decode( _decoder, reinterpret_cast<const unsigned char*>(encodedBuffer.data()), encodedBuffer.length(), reinterpret_cast<opus_int16*>(decodedBuffer.data()), frame_size, 1 );
if ( frames >= 0 ) {
qCDebug(decoder) << "Decoded " << frames << " Opus frames";
decodedBuffer.resize( frames * static_cast<int>(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<int>(sizeof( opus_int16 ));
int frames = opus_decode( _decoder, nullptr, 0, reinterpret_cast<opus_int16*>(decodedBuffer.data()), frame_size, 1 );
if ( frames >= 0 ) {
qCDebug(decoder) << "Produced " << frames << " opus frames from a lost frame";
decodedBuffer.resize( frames * static_cast<int>(sizeof( opus_int16 )) * _opus_num_channels );
} else {
qCCritical(decoder) << "Failed to decode lost frame: " << error_to_string(frames);
}
}

View file

@ -0,0 +1,28 @@
#ifndef OPUSDECODER_H
#define OPUSDECODER_H
#include <plugins/CodecPlugin.h>
#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

View file

@ -0,0 +1,270 @@
#include <PerfStat.h>
#include <QtCore/QLoggingCategory>
#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<int>(sizeof(opus_int16));
int bytes = opus_encode(_encoder, reinterpret_cast<const opus_int16*>(decodedBuffer.data()), frame_size, reinterpret_cast<unsigned char*>(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);
}

View file

@ -0,0 +1,69 @@
#ifndef OPUSENCODER_H
#define OPUSENCODER_H
#include <plugins/CodecPlugin.h>
#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