Skip to content

Commit

Permalink
Add support for the Opus codec
Browse files Browse the repository at this point in the history
Closes LubosD#101
  • Loading branch information
fbriere committed Feb 11, 2022
1 parent db6a6db commit a599bc5
Show file tree
Hide file tree
Showing 23 changed files with 1,401 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ env:
-DWITH_QT5=ON
-DWITH_ALSA=ON
-DWITH_GSM=ON
-DWITH_OPUS=ON
-DWITH_SPEEX=ON
-DWITH_ZRTP=OFF
# Essential packages required by all builds
Expand All @@ -17,6 +18,7 @@ env:
flex
libccrtp-dev
libmagic-dev
libopus-dev
libreadline-dev
libsndfile1-dev
libucommon-dev
Expand Down Expand Up @@ -72,6 +74,7 @@ jobs:
-DWITH_QT5=OFF
-DWITH_ALSA=OFF
-DWITH_GSM=OFF
-DWITH_OPUS=OFF
-DWITH_SPEEX=OFF
-DWITH_ZRTP=OFF
# The empty string would evaluate to false and fail to override
Expand Down
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ include(CMakeDependentOption)

OPTION(WITH_ZRTP "Enable ZRTP encrypted calls" OFF)
OPTION(WITH_SPEEX "Enable the Speex codec" OFF)
OPTION(WITH_OPUS "Enable the Opus codec" OFF)
OPTION(WITH_ILBC "Enable the iLBC codec" OFF)
OPTION(WITH_ALSA "Enable ALSA support" ON)
OPTION(WITH_DIAMONDCARD "Enable Diamondcard integration" OFF)
Expand All @@ -31,6 +32,8 @@ include (CMakePushCheckState)
include (CheckCXXSourceCompiles)
include (TestBigEndian)

find_package(PkgConfig REQUIRED)

find_package(LibXml2 REQUIRED)
find_package(LibMagic REQUIRED)
find_package(LibSndfile REQUIRED)
Expand Down Expand Up @@ -94,6 +97,11 @@ if (WITH_SPEEX)
endif (SPEEX_FOUND)
endif (WITH_SPEEX)

if (WITH_OPUS)
pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus)
set(HAVE_OPUS TRUE)
endif (WITH_OPUS)

if (WITH_ILBC)
find_package(Ilbc)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The following tools are also required:
* libzrtpcpp (version >= 0.9.0) [ZRTP library, ccRTP support must be enabled](http://www.gnutelephony.org/index.php/GNU_ZRTP)
* bcg729 [G.729A codec library](http://www.linphone.org/technical-corner/bcg729)
* Speex and SpeexDSP [Speex codec library](http://www.speex.org/)
* libopus [Opus codec library](http://opus-codec.org/)
* iLBC [iLBC codec library](http://www.ilbcfreeware.org/)

## Build
Expand All @@ -45,6 +46,7 @@ All possible options are:
* ZRTP support: `-DWITH_ZRTP=On`
* G.729A codec support: `-DWITH_G729=On`
* Speex codec support: `-DWITH_SPEEX=On`
* Opus codec support: `-DWITH_OPUS=On`
* iLBC codec support: `-DWITH_ILBC=On`
* Diamondcard support: `-DWITH_DIAMONDCARD=On` (currently broken)

Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ set(twinkle_LIBS
if (WITH_GSM)
list(APPEND twinkle_LIBS ${GSM_LIBRARY})
endif (WITH_GSM)
if (WITH_OPUS)
list(APPEND twinkle_LIBS PkgConfig::Opus)
endif (WITH_OPUS)

if (WITH_QT5)
add_subdirectory(gui)
Expand Down
39 changes: 39 additions & 0 deletions src/audio/audio_codecs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
*/

#include <cstdlib>
#include <sstream>
#include "audio_codecs.h"
#include "userintf.h"

unsigned short audio_sample_rate(t_audio_codec codec) {
switch(codec) {
Expand All @@ -37,6 +39,8 @@ unsigned short audio_sample_rate(t_audio_codec codec) {
return 16000;
case CODEC_SPEEX_UWB:
return 32000;
case CODEC_OPUS:
return 48000;
default:
// Use 8000 as default rate
return 8000;
Expand Down Expand Up @@ -112,3 +116,38 @@ short mix_linear_pcm(short pcm1, short pcm2) {

return short(mixed_sample);
}

#ifdef HAVE_OPUS
unsigned short opus_adjusted_ptime(unsigned short ptime) {
if (ptime <= 10) {
return 10;
} else if (ptime <= 20) {
return 20;
} else if (ptime <= 40) {
return 40;
} else if (ptime <= 60) {
return 60;
} else {
// Maximum duration of an Opus frame
// (Although libopus v1.2+ allows for values up to 120 ms, it
// merely encodes multiple frames into a single Opus packet)
return 60;
}
}

void log_opus_error(
const std::string &func_name,
const std::string &msg,
int opus_error,
bool display_msg)
{
std::stringstream ss;
ss << "Opus error: " << msg << ": " << opus_strerror(opus_error);
std::string s = ss.str();

log_file->write_report(s, func_name, LOG_NORMAL, LOG_CRITICAL);
if (display_msg) {
ui->cb_display_msg(s, MSG_CRITICAL);
}
}
#endif
32 changes: 32 additions & 0 deletions src/audio/audio_codecs.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
#ifndef _AUDIO_CODECS_H
#define _AUDIO_CODECS_H

#include <string>
#include "twinkle_config.h"
#include "g711.h"
#include "g72x.h"
#include "log.h"

// Audio codecs
enum t_audio_codec {
Expand All @@ -31,6 +34,7 @@ enum t_audio_codec {
CODEC_SPEEX_NB,
CODEC_SPEEX_WB,
CODEC_SPEEX_UWB,
CODEC_OPUS,
CODEC_ILBC,
CODEC_G722,
CODEC_G726_16,
Expand All @@ -41,9 +45,24 @@ enum t_audio_codec {
CODEC_G729A
};

enum t_opus_bandwidth_sample_rate {
OPUS_SAMPLE_RATE_NB = 8000,
OPUS_SAMPLE_RATE_MB = 12000,
OPUS_SAMPLE_RATE_WB = 16000,
OPUS_SAMPLE_RATE_SWB = 24000,
OPUS_SAMPLE_RATE_FB = 48000,
};

// Format specific parameters, received on a "a=fmtp:" line during the SDP
// negotiation, to be passed to the corresponding encoder/decoder
struct t_codec_sdp_params {
#ifdef HAVE_OPUS
bool opus_cbr = 0;
unsigned int opus_maxplaybackrate = 0;
unsigned int opus_maxaveragebitrate = 0;
bool opus_useinbandfec = 0;
bool opus_usedtx = 0;
#endif
};

// Default ptime values (ms) for audio codecs
Expand All @@ -53,6 +72,7 @@ struct t_codec_sdp_params {
#define PTIME_G726 20
#define PTIME_GSM 20
#define PTIME_SPEEX 20
#define PTIME_OPUS 20
#define MIN_PTIME 10
#define MAX_PTIME 80

Expand Down Expand Up @@ -116,4 +136,16 @@ int resample(short *input_buf, int input_len, int input_sample_rate,
// Mix 2 16 bits signed linear PCM values
short mix_linear_pcm(short pcm1, short pcm2);

#ifdef HAVE_OPUS
// Bump an arbitrary ptime to the next value allowed by the Opus codec
unsigned short opus_adjusted_ptime(unsigned short ptime);

// Log an Opus error
void log_opus_error(
const std::string &func_name,
const std::string &msg,
int opus_error,
bool display_msg = true);
#endif

#endif
74 changes: 74 additions & 0 deletions src/audio/audio_decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,80 @@ bool t_speex_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sampl
}
#endif

#ifdef HAVE_OPUS
//////////////////////////////////////////
// class t_opus_audio_decoder
//////////////////////////////////////////

t_opus_audio_decoder::t_opus_audio_decoder(uint16 default_ptime, t_user *user_config) :
t_audio_decoder(default_ptime, true, user_config)
{
_codec = CODEC_OPUS;
if (_default_ptime == 0) {
_default_ptime = PTIME_OPUS;
}
_frame_size = audio_sample_rate(_codec) * _default_ptime / 1000;

int error;
dec = opus_decoder_create(audio_sample_rate(_codec), 1, &error);
if (error != OPUS_OK) {
log_opus_error("t_opus_audio_decoder::t_opus_audio_decoder",
"Cannot create decoder", error);
dec = NULL;
return;
}
}

t_opus_audio_decoder::~t_opus_audio_decoder() {
if (dec) {
opus_decoder_destroy(dec);
}
}

uint16 t_opus_audio_decoder::get_ptime(uint16 payload_size) const {
return get_default_ptime();
}

uint16 t_opus_audio_decoder::decode(uint8 *payload, uint16 payload_size,
int16 *pcm_buf, uint16 pcm_buf_size)
{
if (!dec) return 0;

assert(pcm_buf_size >= _frame_size);

int nsamples = opus_decode(dec, payload, payload_size, pcm_buf, pcm_buf_size, 0);

if (nsamples < 0) {
log_opus_error("t_opus_audio_decoder::decode",
"Error while decoding", nsamples);
return 0;
}

return nsamples;
}

uint16 t_opus_audio_decoder::conceal(int16 *pcm_buf, uint16 pcm_buf_size) {
if (!dec) return 0;

assert(pcm_buf_size >= _frame_size);

int nsamples = opus_decode(dec, NULL, 0, pcm_buf, _frame_size, 0);

if (nsamples < 0) {
log_opus_error("t_opus_audio_decoder::conceal",
"Error while concealing missing frame", nsamples);
return 0;
}

return nsamples;
}

bool t_opus_audio_decoder::valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const
{
return true;
}
#endif

#ifdef HAVE_ILBC
//////////////////////////////////////////
// class t_ilbc_audio_decoder
Expand Down
23 changes: 23 additions & 0 deletions src/audio/audio_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ extern "C" {
#include <speex/speex_preprocess.h>
#endif

#ifdef HAVE_OPUS
#include <opus/opus.h>
#endif

extern "C" {
# include "g722.h"
# include "g722_local.h"
Expand Down Expand Up @@ -157,6 +161,25 @@ class t_speex_audio_decoder : public t_audio_decoder {
};
#endif

#ifdef HAVE_OPUS
// Opus
class t_opus_audio_decoder : public t_audio_decoder {
private:
OpusDecoder *dec;
unsigned short _frame_size; // Number of samples per frame

public:
t_opus_audio_decoder(uint16 default_ptime, t_user *user_config);
virtual ~t_opus_audio_decoder();

virtual uint16 get_ptime(uint16 payload_size) const;
virtual uint16 decode(uint8 *payload, uint16 payload_size,
int16 *pcm_buf, uint16 pcm_buf_size);
virtual uint16 conceal(int16 *pcm_buf, uint16 pcm_buf_size);
virtual bool valid_payload_size(uint16 payload_size, uint16 sample_buf_size) const;
};
#endif

#ifdef HAVE_ILBC
// iLBC
class t_ilbc_audio_decoder : public t_audio_decoder {
Expand Down
Loading

0 comments on commit a599bc5

Please sign in to comment.