diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b638973..c528691d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 @@ -17,6 +18,7 @@ env: flex libccrtp-dev libmagic-dev + libopus-dev libreadline-dev libsndfile1-dev libucommon-dev @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e3fde02..b199f646 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -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) diff --git a/README.md b/README.md index c22c0fcc..525942fe 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0770bce9..23d5759d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/audio/audio_codecs.cpp b/src/audio/audio_codecs.cpp index 37468a55..1e56dda9 100644 --- a/src/audio/audio_codecs.cpp +++ b/src/audio/audio_codecs.cpp @@ -16,7 +16,9 @@ */ #include +#include #include "audio_codecs.h" +#include "userintf.h" unsigned short audio_sample_rate(t_audio_codec codec) { switch(codec) { @@ -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; @@ -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 diff --git a/src/audio/audio_codecs.h b/src/audio/audio_codecs.h index ba158064..c5dcac34 100644 --- a/src/audio/audio_codecs.h +++ b/src/audio/audio_codecs.h @@ -18,8 +18,11 @@ #ifndef _AUDIO_CODECS_H #define _AUDIO_CODECS_H +#include +#include "twinkle_config.h" #include "g711.h" #include "g72x.h" +#include "log.h" // Audio codecs enum t_audio_codec { @@ -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, @@ -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 @@ -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 @@ -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 diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index d81f8c35..93d64964 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -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 diff --git a/src/audio/audio_decoder.h b/src/audio/audio_decoder.h index bd3b9ee3..22140ee7 100644 --- a/src/audio/audio_decoder.h +++ b/src/audio/audio_decoder.h @@ -37,6 +37,10 @@ extern "C" { #include #endif +#ifdef HAVE_OPUS +#include +#endif + extern "C" { # include "g722.h" # include "g722_local.h" @@ -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 { diff --git a/src/audio/audio_encoder.cpp b/src/audio/audio_encoder.cpp index 31e8aaf1..2c491248 100644 --- a/src/audio/audio_encoder.cpp +++ b/src/audio/audio_encoder.cpp @@ -243,6 +243,232 @@ uint16 t_speex_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, } #endif +#ifdef HAVE_OPUS +////////////////////////////////////////// +// class t_opus_audio_encoder +////////////////////////////////////////// + +t_opus_audio_encoder::t_opus_audio_encoder(uint16 payload_id, uint16 ptime, + t_user *user_config, const t_codec_sdp_params &sdp_params) : + t_audio_encoder(payload_id, ptime, user_config) +{ + _codec = CODEC_OPUS; + if (_ptime == 0) { + _ptime = PTIME_OPUS; + } + // Value suggested by the Opus documentation (enough to hold 60 ms of + // a 510 kbps stream) + _max_payload_size = 4000; + + int error; + enc = opus_encoder_create(audio_sample_rate(_codec), 1, // (1 = mono) + OPUS_APPLICATION_VOIP, &error); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot create encoder", error); + enc = NULL; + return; + } + + // Encoder parameters + + // Bandwidth + log_file->write_header("t_opus_audio_encoder::t_opus_audio_encoder", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Determining bandwidth\n"); + log_file->write_raw("(Values are OPUS_BANDWIDTH_* constants)\n"); + opus_int32 bandwidth = _user_config->get_opus_bandwidth(); + log_file->write_raw("Initial bandwidth = "); + log_file->write_raw(bandwidth); + log_file->write_endl(); + // Apply maximum bandwidth according to "maxplaybackrate" (mandated by + // RFC 7587, s. 7.1) + unsigned int maxplaybackrate = sdp_params.opus_maxplaybackrate; + if (maxplaybackrate == 0) { + maxplaybackrate = 48000; // Default value + } + if (maxplaybackrate < 48000) { + opus_int32 max_bandwidth; + if (maxplaybackrate <= OPUS_SAMPLE_RATE_NB) { + max_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + } else if (maxplaybackrate <= OPUS_SAMPLE_RATE_MB) { + max_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + } else if (maxplaybackrate <= OPUS_SAMPLE_RATE_WB) { + max_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + } else if (maxplaybackrate <= OPUS_SAMPLE_RATE_SWB) { + max_bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + } else { + max_bandwidth = OPUS_BANDWIDTH_FULLBAND; + } + log_file->write_raw("Maximum bandwidth ("); + log_file->write_raw(maxplaybackrate); + log_file->write_raw(") = "); + log_file->write_raw(max_bandwidth); + log_file->write_endl(); + // FIXME: OPUS_BANDWIDTH_* are not *guaranteed* to be ordered + bandwidth = std::min(bandwidth, max_bandwidth); + } + log_file->write_raw("Final bandwidth = "); + log_file->write_raw(bandwidth); + log_file->write_endl(); + log_file->write_footer(); + error = opus_encoder_ctl(enc, OPUS_SET_MAX_BANDWIDTH(bandwidth)); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set maximum bandwidth", error); + } + + // Bitrate + log_file->write_header("t_opus_audio_encoder::t_opus_audio_encoder", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Determining bitrate\n"); + opus_int32 bitrate; + log_file->write_raw("Initial bitrate = "); + if (_user_config->get_opus_bitrate() == 0) { + bitrate = OPUS_AUTO; + log_file->write_raw("auto"); + } else { + bitrate = _user_config->get_opus_bitrate(); + log_file->write_raw(bitrate); + } + log_file->write_endl(); + // maxaveragebitrate dictates the maximum avg. bitrate we can use + unsigned int maxaveragebitrate = sdp_params.opus_maxaveragebitrate; + log_file->write_raw("Initial maxaveragebitrate = "); + log_file->write_raw(maxaveragebitrate); + log_file->write_endl(); + // If not provided, its value defaults to "the maximum value specified + // in Section 3.1.1 for the corresponding mode of Opus and + // corresponding maxplaybackrate", per RFC 7587, s. 6.1 + if (maxaveragebitrate == 0) { + log_file->write_raw("Default maxaveragebitrate ("); + + switch (_user_config->get_opus_signal_type()) { + case OPUS_AUTO: + case OPUS_SIGNAL_VOICE: + log_file->write_raw("voice, "); + if (maxplaybackrate <= OPUS_SAMPLE_RATE_NB) { + log_file->write_raw("NB"); + // 8-12 kbit/s for NB speech + maxaveragebitrate = 12000; + } else if (maxplaybackrate <= OPUS_SAMPLE_RATE_WB) { + log_file->write_raw("WB"); + // 16-20 kbit/s for WB speech + maxaveragebitrate = 20000; + } else { + log_file->write_raw("FB"); + // 28-40 kbit/s for FB speech + maxaveragebitrate = 40000; + } + break; + case OPUS_SIGNAL_MUSIC: + log_file->write_raw("music, FB"); + // 48-64 kbit/s for FB mono music + maxaveragebitrate = 64000; + break; + default: + assert(false); + } + + log_file->write_raw(") = "); + log_file->write_raw(maxaveragebitrate); + log_file->write_endl(); + } + // When bitrate is "auto", we must take care not to raise its + // value if maxaveragebitrate happens to be higher than the + // actual bitrate, so we convert it to a real value + if (bitrate == OPUS_AUTO) { + // Formula copied from the libopus source code + const unsigned short Fs = audio_sample_rate(_codec); + const unsigned short frame_size = Fs * ptime / 1000; + const unsigned channels = 1; + bitrate = 60*Fs/frame_size + Fs*channels; + + log_file->write_raw("Auto bitrate = "); + log_file->write_raw(bitrate); + log_file->write_endl(); + } + bitrate = std::min(bitrate, (int)maxaveragebitrate); + log_file->write_raw("Final bitrate = "); + log_file->write_raw(bitrate); + log_file->write_endl(); + log_file->write_footer(); + error = opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set bitrate", error); + } + + // Complexity + error = opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(_user_config->get_opus_complexity())); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set complexity", error); + } + + // DTX + error = opus_encoder_ctl(enc, OPUS_SET_DTX(sdp_params.opus_usedtx ? 1 : 0)); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set DTX", error); + } + + // FEC + error = opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(sdp_params.opus_useinbandfec ? 1 : 0)); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set FEC", error); + } + + // Packet loss + error = opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(_user_config->get_opus_packet_loss())); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set packet loss", error); + } + + // Signal type + error = opus_encoder_ctl(enc, OPUS_SET_SIGNAL(_user_config->get_opus_signal_type())); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set signal type", error); + } + + // Constant/variable bitrate + error = opus_encoder_ctl(enc, OPUS_SET_VBR(!sdp_params.opus_cbr ? 1 : 0)); + if (error != OPUS_OK) { + log_opus_error("t_opus_audio_encoder::t_opus_audio_encoder", + "Cannot set VBR", error); + } +} + +t_opus_audio_encoder::~t_opus_audio_encoder() { + if (enc) { + opus_encoder_destroy(enc); + } +} + +uint16 t_opus_audio_encoder::encode(int16 *sample_buf, uint16 nsamples, + uint8 *payload, uint16 payload_size, bool &silence) +{ + if (!enc) return 0; + + assert(payload_size >= _max_payload_size); + + silence = false; + + int nbytes = opus_encode(enc, sample_buf, nsamples, payload, payload_size); + + if (nbytes < 0) { + log_opus_error("t_opus_audio_encoder::encode", + "Error while encoding", nbytes); + return 0; + } else if (nbytes <= 2) { + silence = true; // DTX + } + + return nbytes; +} +#endif + #ifdef HAVE_ILBC ////////////////////////////////////////// // class t_ilbc_audio_encoder diff --git a/src/audio/audio_encoder.h b/src/audio/audio_encoder.h index 0acf87b0..44efc66f 100644 --- a/src/audio/audio_encoder.h +++ b/src/audio/audio_encoder.h @@ -36,6 +36,10 @@ extern "C" { #include #endif +#ifdef HAVE_OPUS +#include +#endif + extern "C" { # include "g722.h" # include "g722_local.h" @@ -147,6 +151,21 @@ class t_speex_audio_encoder : public t_audio_encoder { }; #endif +#ifdef HAVE_OPUS +class t_opus_audio_encoder : public t_audio_encoder { +private: + OpusEncoder *enc; + +public: + t_opus_audio_encoder(uint16 payload_id, uint16 ptime, + t_user *user_config, const t_codec_sdp_params &codec_sdp_params); + virtual ~t_opus_audio_encoder(); + + virtual uint16 encode(int16 *sample_buf, uint16 nsamples, + uint8 *payload, uint16 payload_size, bool &silence); +}; +#endif + #ifdef HAVE_ILBC class t_ilbc_audio_encoder : public t_audio_encoder { private: diff --git a/src/audio/audio_rx.cpp b/src/audio/audio_rx.cpp index 3e807d26..c9c1d6f3 100644 --- a/src/audio/audio_rx.cpp +++ b/src/audio/audio_rx.cpp @@ -315,6 +315,12 @@ t_audio_rx::t_audio_rx(t_audio_session *_audio_session, MEMMAN_NEW(audio_encoder); break; #endif +#ifdef HAVE_OPUS + case CODEC_OPUS: + audio_encoder = new t_opus_audio_encoder(_payload_id, _ptime, user_config, audio_session->get_codec_sdp_params()); + MEMMAN_NEW(audio_encoder); + break; +#endif #ifdef HAVE_ILBC case CODEC_ILBC: audio_encoder = new t_ilbc_audio_encoder(_payload_id, _ptime, user_config); diff --git a/src/audio/audio_tx.cpp b/src/audio/audio_tx.cpp index 1816512c..9ca7fc69 100644 --- a/src/audio/audio_tx.cpp +++ b/src/audio/audio_tx.cpp @@ -90,6 +90,10 @@ t_audio_tx::t_audio_tx(t_audio_session *_audio_session, t_speex_audio_decoder::MODE_UWB, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_SPEEX_UWB]); #endif +#ifdef HAVE_OPUS + map_audio_decoder[CODEC_OPUS] = new t_opus_audio_decoder(_ptime, user_config); + MEMMAN_NEW(map_audio_decoder[CODEC_OPUS]); +#endif #ifdef HAVE_ILBC map_audio_decoder[CODEC_ILBC] = new t_ilbc_audio_decoder(_ptime, user_config); MEMMAN_NEW(map_audio_decoder[CODEC_ILBC]); diff --git a/src/gui/userprofileform.cpp b/src/gui/userprofileform.cpp index 4a03913d..5b7abbaa 100644 --- a/src/gui/userprofileform.cpp +++ b/src/gui/userprofileform.cpp @@ -36,6 +36,10 @@ #include "util.h" #include "userprofileform.h" +#ifdef HAVE_OPUS +#include +#endif + // Indices of categories in the category list box #define idxCatUser 0 @@ -67,8 +71,9 @@ #define idxRtpPreprocessing 1 #define idxRtpIlbc 2 #define idxRtpSpeex 3 -#define idxRtpG726 4 -#define idxRtpDtmf 5 +#define idxRtpOpus 4 +#define idxRtpG726 5 +#define idxRtpDtmf 6 // Codec labels #define labelCodecG711a "G.711 A-law" @@ -77,6 +82,7 @@ #define labelCodecSpeexNb "speex-nb (8 kHz)" #define labelCodecSpeexWb "speex-wb (16 kHz)" #define labelCodecSpeexUwb "speex-uwb (32 kHz)" +#define labelCodecOpus "Opus" #define labelCodecIlbc "iLBC" #define labelCodecG722 "G.722" #define labelCodecG726_16 "G.726 16 kbps" @@ -89,6 +95,18 @@ #define idxIlbcMode20 0 #define idxIlbcMode30 1 +// Indices of Opus bandwidths +#define idxOpusBandwidthNB 0 +#define idxOpusBandwidthMB 1 +#define idxOpusBandwidthWB 2 +#define idxOpusBandwidthSWB 3 +#define idxOpusBandwidthFB 4 + +// Indices of Opus signal types +#define idxOpusSignalTypeAuto 0 +#define idxOpusSignalTypeVoice 1 +#define idxOpusSignalTypeMusic 2 + // Indices of G.726 packing modes #define idxG726PackRfc3551 0 #define idxG726PackAal2 1 @@ -180,6 +198,11 @@ void UserProfileForm::init() rtpAudioTabWidget->setTabEnabled(idxRtpSpeex, false); rtpAudioTabWidget->setTabEnabled(idxRtpPreprocessing, false); #endif +#ifndef HAVE_OPUS + // Opus + opusGroupBox->hide(); + rtpAudioTabWidget->setTabEnabled(idxRtpOpus, false); +#endif #ifndef HAVE_ILBC // iLBC ilbcGroupBox->hide(); @@ -245,6 +268,8 @@ t_audio_codec UserProfileForm::label2codec(const QString &label) { return CODEC_SPEEX_WB; } else if (label == labelCodecSpeexUwb) { return CODEC_SPEEX_UWB; + } else if (label == labelCodecOpus) { + return CODEC_OPUS; } else if (label == labelCodecIlbc) { return CODEC_ILBC; } else if (label == labelCodecG722) { @@ -278,6 +303,8 @@ QString UserProfileForm::codec2label(t_audio_codec &codec) { return labelCodecSpeexWb; case CODEC_SPEEX_UWB: return labelCodecSpeexUwb; + case CODEC_OPUS: + return labelCodecOpus; case CODEC_ILBC: return labelCodecIlbc; case CODEC_G722: @@ -422,6 +449,9 @@ void UserProfileForm::populate() allCodecs.append(labelCodecSpeexWb); allCodecs.append(labelCodecSpeexUwb); #endif +#ifdef HAVE_OPUS + allCodecs.append(labelCodecOpus); +#endif #ifdef HAVE_ILBC allCodecs.append(labelCodecIlbc); #endif @@ -469,6 +499,56 @@ void UserProfileForm::populate() spxWbPayloadSpinBox->setValue(current_profile->get_speex_wb_payload_type()); spxUwbPayloadSpinBox->setValue(current_profile->get_speex_uwb_payload_type()); +#ifdef HAVE_OPUS + // Opus + switch (current_profile->get_opus_bandwidth()) { + case OPUS_BANDWIDTH_NARROWBAND: + opusBandwidthComboBox->setCurrentIndex(idxOpusBandwidthNB); + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + opusBandwidthComboBox->setCurrentIndex(idxOpusBandwidthMB); + break; + case OPUS_BANDWIDTH_WIDEBAND: + opusBandwidthComboBox->setCurrentIndex(idxOpusBandwidthWB); + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + opusBandwidthComboBox->setCurrentIndex(idxOpusBandwidthSWB); + break; + case OPUS_BANDWIDTH_FULLBAND: + opusBandwidthComboBox->setCurrentIndex(idxOpusBandwidthFB); + break; + default: + opusBandwidthComboBox->setCurrentIndex(idxOpusBandwidthFB); + break; + } + if (current_profile->get_opus_bitrate() == 0) { + opusBitrateAutoRadioButton->setChecked(true); + } else { + opusBitrateValueRadioButton->setChecked(true); + opusBitrateValueSpinBox->setValue(current_profile->get_opus_bitrate() / 1000); + } + opusCbrCheckBox->setChecked(current_profile->get_opus_cbr()); + opusComplexitySpinBox->setValue(current_profile->get_opus_complexity()); + opusDtxCheckBox->setChecked(current_profile->get_opus_dtx()); + opusFecCheckBox->setChecked(current_profile->get_opus_fec()); + opusPacketLossSpinBox->setValue(current_profile->get_opus_packet_loss()); + opusPayloadSpinBox->setValue(current_profile->get_opus_payload_type()); + switch (current_profile->get_opus_signal_type()) { + case OPUS_AUTO: + opusSignalTypeComboBox->setCurrentIndex(idxOpusSignalTypeAuto); + break; + case OPUS_SIGNAL_VOICE: + opusSignalTypeComboBox->setCurrentIndex(idxOpusSignalTypeVoice); + break; + case OPUS_SIGNAL_MUSIC: + opusSignalTypeComboBox->setCurrentIndex(idxOpusSignalTypeMusic); + break; + default: + opusSignalTypeComboBox->setCurrentIndex(idxOpusSignalTypeAuto); + break; + } +#endif + // iLBC ilbcPayloadSpinBox->setValue(current_profile->get_ilbc_payload_type()); @@ -889,6 +969,13 @@ bool UserProfileForm::validateValues() return false; } +#ifdef HAVE_OPUS + if (!check_dynamic_payload(opusPayloadSpinBox, checked_types)) { + rtpAudioTabWidget->setCurrentWidget(tabOpus); + return false; + } +#endif + if (!check_dynamic_payload(ilbcPayloadSpinBox, checked_types)) { rtpAudioTabWidget->setCurrentWidget(tabIlbc); return false; @@ -1091,6 +1178,55 @@ bool UserProfileForm::validateValues() current_profile->set_speex_wb_payload_type(spxWbPayloadSpinBox->value()); current_profile->set_speex_uwb_payload_type(spxUwbPayloadSpinBox->value()); +#ifdef HAVE_OPUS + // Opus + switch (opusBandwidthComboBox->currentIndex()) { + case idxOpusBandwidthNB: + current_profile->set_opus_bandwidth(OPUS_BANDWIDTH_NARROWBAND); + break; + case idxOpusBandwidthMB: + current_profile->set_opus_bandwidth(OPUS_BANDWIDTH_MEDIUMBAND); + break; + case idxOpusBandwidthWB: + current_profile->set_opus_bandwidth(OPUS_BANDWIDTH_WIDEBAND); + break; + case idxOpusBandwidthSWB: + current_profile->set_opus_bandwidth(OPUS_BANDWIDTH_SUPERWIDEBAND); + break; + case idxOpusBandwidthFB: + current_profile->set_opus_bandwidth(OPUS_BANDWIDTH_FULLBAND); + break; + default: + current_profile->set_opus_bandwidth(OPUS_BANDWIDTH_FULLBAND); + break; + } + if (opusBitrateAutoRadioButton->isChecked()) { + current_profile->set_opus_bitrate(0); + } else { + current_profile->set_opus_bitrate(opusBitrateValueSpinBox->value() * 1000); + } + current_profile->set_opus_cbr(opusCbrCheckBox->isChecked()); + current_profile->set_opus_complexity(opusComplexitySpinBox->value()); + current_profile->set_opus_dtx(opusDtxCheckBox->isChecked()); + current_profile->set_opus_fec(opusFecCheckBox->isChecked()); + current_profile->set_opus_packet_loss(opusPacketLossSpinBox->value()); + current_profile->set_opus_payload_type(opusPayloadSpinBox->value()); + switch (opusSignalTypeComboBox->currentIndex()) { + case idxOpusSignalTypeAuto: + current_profile->set_opus_signal_type(OPUS_AUTO); + break; + case idxOpusSignalTypeVoice: + current_profile->set_opus_signal_type(OPUS_SIGNAL_VOICE); + break; + case idxOpusSignalTypeMusic: + current_profile->set_opus_signal_type(OPUS_SIGNAL_MUSIC); + break; + default: + current_profile->set_opus_signal_type(OPUS_AUTO); + break; + } +#endif + // iLBC current_profile->set_ilbc_payload_type(ilbcPayloadSpinBox->value()); switch (ilbcPayloadSizeComboBox->currentIndex()) { diff --git a/src/gui/userprofileform.ui b/src/gui/userprofileform.ui index 0ba759d1..7e4cd780 100644 --- a/src/gui/userprofileform.ui +++ b/src/gui/userprofileform.ui @@ -786,7 +786,7 @@ This field is mandatory. - &G.711/G.722/G.726 payload size: + &G.711/G.722/G.726/Opus payload size: false @@ -817,7 +817,7 @@ This field is mandatory. - The preferred payload size for the G.711, G.722 and G.726 codecs. + The preferred payload size for the G.711, G.722, G.726 and Opus codecs. 10 @@ -1586,6 +1586,346 @@ If you disable this option, then the first codec from the active codecs that is + + + Opus + + + + + + Opus + + + + + + + + Common maximum parameters + + + + + + + + Bitrate: + + + + + + + Auto + + + true + + + buttonGroup + + + + + + + Custom: + + + buttonGroup + + + + + + + false + + + true + + + false + + + kbps + + + 6 + + + 510 + + + 10 + + + 510 + + + + + + + + + + + Bandwidth: + + + + + + + 4 + + + + Narrowband (4 kHz) + + + + + Medium-band (6 kHz) + + + + + Wideband (8 kHz) + + + + + Super-wideband (12 kHz) + + + + + Fullband (20 kHz) + + + + + + + + + + + + + + + Local parameters + + + + + + + + Signal type: + + + + + + + + Auto + + + + + Voice + + + + + Music + + + + + + + + + + + + + + + % + + + 0 + + + 100 + + + 10 + + + + + + + + + + 0 + + + 10 + + + 10 + + + + + + + Complexity: + + + false + + + opusComplexitySpinBox + + + + + + + Packet loss: + + + false + + + opusPacketLossSpinBox + + + + + + + + + + + + Remote parameters + + + + + + + + + Constant bitrate + + + + + + + + + + Discontinuous transmission + + + + + + + false + + + + + + Forward error correction + + + + + + + + + + + + + + Payload type: + + + false + + + opusPayloadSpinBox + + + + + + + The dynamic type value (96 or higher) to be used for Opus. + + + 96 + + + 127 + + + + + + + + + + + Qt::Horizontal + + + + 69 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 147 + + + + + + G.726 @@ -5518,5 +5858,24 @@ Solicited message waiting indication as specified by RFC 3842. + + opusBitrateValueRadioButton + toggled(bool) + opusBitrateValueSpinBox + setEnabled(bool) + + + 456 + 289 + + + 517 + 290 + + + + + + diff --git a/src/sdp/sdp.cpp b/src/sdp/sdp.cpp index 55e18f86..f9357529 100644 --- a/src/sdp/sdp.cpp +++ b/src/sdp/sdp.cpp @@ -122,6 +122,9 @@ string get_rtpmap(unsigned format, t_audio_codec codec) { case CODEC_SPEEX_UWB: rtpmap += SDP_RTPMAP_SPEEX_UWB; break; + case CODEC_OPUS: + rtpmap += SDP_RTPMAP_OPUS; + break; case CODEC_ILBC: rtpmap += SDP_RTPMAP_ILBC; break; @@ -657,6 +660,8 @@ t_audio_codec t_sdp::get_rtpmap_codec(const string &rtpmap) const { return CODEC_SPEEX_WB; } else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 32000) { return CODEC_SPEEX_UWB; + } else if (cmp_nocase(codec_name, SDP_AC_NAME_OPUS) == 0 && sample_rate == 48000 && channels == 2) { + return CODEC_OPUS; } else if (cmp_nocase(codec_name, SDP_AC_NAME_ILBC) == 0 && sample_rate == 8000) { return CODEC_ILBC; } else if (cmp_nocase(codec_name, SDP_AC_NAME_G722) == 0 && sample_rate == 8000) { diff --git a/src/sdp/sdp.h b/src/sdp/sdp.h index fc0881ad..fa9081d6 100644 --- a/src/sdp/sdp.h +++ b/src/sdp/sdp.h @@ -43,6 +43,7 @@ #define SDP_RTPMAP_SPEEX_NB "speex/8000" #define SDP_RTPMAP_SPEEX_WB "speex/16000" #define SDP_RTPMAP_SPEEX_UWB "speex/32000" +#define SDP_RTPMAP_OPUS "opus/48000/2" #define SDP_RTPMAP_ILBC "iLBC/8000" #define SDP_RTPMAP_G722 "G722/8000" #define SDP_RTPMAP_G726_16 "G726-16/8000" @@ -57,6 +58,7 @@ #define SDP_AC_NAME_G711_ALAW "PCMA" #define SDP_AC_NAME_GSM "GSM" #define SDP_AC_NAME_SPEEX "speex" +#define SDP_AC_NAME_OPUS "opus" #define SDP_AC_NAME_ILBC "iLBC" #define SDP_AC_NAME_G722 "G722" #define SDP_AC_NAME_G726_16 "G726-16" diff --git a/src/session.cpp b/src/session.cpp index 89d96aa1..1717a3b9 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -16,6 +16,7 @@ */ #include +#include "twinkle_config.h" #include "line.h" #include "log.h" #include "phone.h" @@ -121,6 +122,9 @@ t_session::t_session(t_dialog *_dialog, string _receive_host, recv_ac2payload[CODEC_SPEEX_NB] = user_config->get_speex_nb_payload_type(); recv_ac2payload[CODEC_SPEEX_WB] = user_config->get_speex_wb_payload_type(); recv_ac2payload[CODEC_SPEEX_UWB] = user_config->get_speex_uwb_payload_type(); +#ifdef HAVE_OPUS + recv_ac2payload[CODEC_OPUS] = user_config->get_opus_payload_type(); +#endif recv_ac2payload[CODEC_ILBC] = user_config->get_ilbc_payload_type(); recv_ac2payload[CODEC_G726_16] = user_config->get_g726_16_payload_type(); recv_ac2payload[CODEC_G726_24] = user_config->get_g726_24_payload_type(); @@ -138,6 +142,9 @@ t_session::t_session(t_dialog *_dialog, string _receive_host, recv_payload2ac[user_config->get_speex_nb_payload_type()] = CODEC_SPEEX_NB; recv_payload2ac[user_config->get_speex_wb_payload_type()] = CODEC_SPEEX_WB; recv_payload2ac[user_config->get_speex_uwb_payload_type()] = CODEC_SPEEX_UWB; +#ifdef HAVE_OPUS + recv_payload2ac[user_config->get_opus_payload_type()] = CODEC_OPUS; +#endif recv_payload2ac[user_config->get_ilbc_payload_type()] = CODEC_ILBC; recv_payload2ac[user_config->get_g726_16_payload_type()] = CODEC_G726_16; recv_payload2ac[user_config->get_g726_24_payload_type()] = CODEC_G726_24; @@ -346,6 +353,10 @@ bool t_session::process_sdp_offer(t_sdp *sdp, int &warn_code, } } + if (use_codec == CODEC_OPUS) { + process_sdp_opus(sdp, warn_code, warn_text); + } + return true; } @@ -416,9 +427,49 @@ bool t_session::process_sdp_answer(t_sdp *sdp, int &warn_code, } } + if (use_codec == CODEC_OPUS) { + process_sdp_opus(sdp, warn_code, warn_text); + } + return true; } +void t_session::process_sdp_opus(t_sdp *sdp, int &warn_code, + std::string &warn_text) +{ +#ifdef HAVE_OPUS + int cbr = sdp->get_fmtp_int_param(SDP_AUDIO, + send_ac2payload[use_codec], "cbr"); + if (cbr != -1) { + codec_sdp_params.opus_cbr = cbr; + } + + int maxaveragebitrate = sdp->get_fmtp_int_param(SDP_AUDIO, + send_ac2payload[use_codec], "maxaveragebitrate"); + if (maxaveragebitrate != -1) { + codec_sdp_params.opus_maxaveragebitrate = maxaveragebitrate; + } + + int maxplaybackrate = sdp->get_fmtp_int_param(SDP_AUDIO, + send_ac2payload[use_codec], "maxplaybackrate"); + if (maxplaybackrate != -1) { + codec_sdp_params.opus_maxplaybackrate = maxplaybackrate; + } + + int useinbandfec = sdp->get_fmtp_int_param(SDP_AUDIO, + send_ac2payload[use_codec], "useinbandfec"); + if (useinbandfec != -1) { + codec_sdp_params.opus_useinbandfec = useinbandfec; + } + + int usedtx = sdp->get_fmtp_int_param(SDP_AUDIO, + send_ac2payload[use_codec], "usedtx"); + if (usedtx != -1) { + codec_sdp_params.opus_usedtx = usedtx; + } +#endif +} + void t_session::create_sdp_offer(t_sip_message *m, const string &user) { // Delete old body if present if (m->body) { @@ -444,7 +495,7 @@ void t_session::create_sdp_offer(t_sip_message *m, const string &user) { MEMMAN_NEW(m->body); - // Set ptime for G.711/G.722/G.726 codecs + // Set ptime for G.711/G.722/G.726/Opus codecs list::iterator it_g7xx; it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G711_ALAW); if (it_g7xx == offer_codecs.end()) { @@ -465,10 +516,19 @@ void t_session::create_sdp_offer(t_sip_message *m, const string &user) { if (it_g7xx == offer_codecs.end()) { it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_G726_40); } + if (it_g7xx == offer_codecs.end()) { + it_g7xx = find(offer_codecs.begin(), offer_codecs.end(), CODEC_OPUS); + } if (it_g7xx != offer_codecs.end()) { ((t_sdp *)m->body)->set_ptime(SDP_AUDIO, ptime); } + list::iterator it_opus; + it_opus = find(offer_codecs.begin(), offer_codecs.end(), CODEC_OPUS); + if (it_opus != offer_codecs.end()) { + create_sdp_opus(m, user); + } + // Set mode for iLBC codecs list::iterator it_ilbc; it_ilbc = find(offer_codecs.begin(), offer_codecs.end(), CODEC_ILBC); @@ -579,13 +639,20 @@ void t_session::create_sdp_answer(t_sip_message *m, const string &user) { // Set audio attributes - // Set ptime for G711 codecs + // Set ptime for G711/Opus codecs if (use_codec == CODEC_G711_ALAW || - use_codec == CODEC_G711_ULAW) + use_codec == CODEC_G711_ULAW || + use_codec == CODEC_OPUS) { ((t_sdp *)m->body)->set_ptime(SDP_AUDIO, ptime); } + list::iterator it_opus; + it_opus = find(offer_codecs.begin(), offer_codecs.end(), CODEC_OPUS); + if (it_opus != offer_codecs.end()) { + create_sdp_opus(m, user); + } + // Set mode for iLBC codecs if (use_codec == CODEC_ILBC && ilbc_mode != 30) { unsigned short ilbc_payload = const_cast(this)-> @@ -605,6 +672,54 @@ void t_session::create_sdp_answer(t_sip_message *m, const string &user) { } } +void t_session::create_sdp_opus(t_sip_message *m, const std::string &user) { +#ifdef HAVE_OPUS + std::map fmtp_params; + + if (user_config->get_opus_cbr()) { + fmtp_params["cbr"] = 1; + } + + unsigned int bitrate = user_config->get_opus_bitrate(); + if (bitrate != 0) { + fmtp_params["maxaveragebitrate"] = bitrate; + } + + t_opus_bandwidth_sample_rate bandwidth_sample_rate; + switch (user_config->get_opus_bandwidth()) { + case OPUS_BANDWIDTH_NARROWBAND: + bandwidth_sample_rate = OPUS_SAMPLE_RATE_NB; + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + bandwidth_sample_rate = OPUS_SAMPLE_RATE_MB; + break; + case OPUS_BANDWIDTH_WIDEBAND: + bandwidth_sample_rate = OPUS_SAMPLE_RATE_WB; + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + bandwidth_sample_rate = OPUS_SAMPLE_RATE_SWB; + break; + case OPUS_BANDWIDTH_FULLBAND: + bandwidth_sample_rate = OPUS_SAMPLE_RATE_FB; + break; + default: + assert(false); + } + if (bandwidth_sample_rate != OPUS_SAMPLE_RATE_FB) { + fmtp_params["maxplaybackrate"] = bandwidth_sample_rate; + } + + // RFC 7587 recommends sending an explicit useinbandfec=0 + fmtp_params["useinbandfec"] = (user_config->get_opus_fec() ? 1 : 0); + + if (user_config->get_opus_dtx()) { + fmtp_params["usedtx"] = 1; + } + + ((t_sdp *)m->body)->set_fmtp_int_params(SDP_AUDIO, recv_ac2payload[CODEC_OPUS], fmtp_params); +#endif +} + void t_session::start_rtp(void) { // If a session is killed, it may not be started again. if (is_killed) { @@ -652,6 +767,10 @@ void t_session::start_rtp(void) { unsigned short audio_ptime; if (use_codec == CODEC_ILBC) { audio_ptime = ilbc_mode; +#ifdef HAVE_OPUS + } else if (use_codec == CODEC_OPUS) { + audio_ptime = opus_adjusted_ptime(ptime); +#endif } else { audio_ptime = ptime; } diff --git a/src/session.h b/src/session.h index 60390e0f..85d21b62 100644 --- a/src/session.h +++ b/src/session.h @@ -74,6 +74,10 @@ class t_session { // Set the list of received codecs from the SDP. // Create the send_ac2paylaod and send_payload2ac mappings. void set_recvd_codecs(t_sdp *sdp); + + // Opus-related sections common to both offer/answer methods + void process_sdp_opus(t_sdp *sdp, int &warn_code, std::string &warn_text); + void create_sdp_opus(t_sip_message *m, const std::string &user); // Returns if this session is part of a 3-way conference bool is_3way(void) const; diff --git a/src/sys_settings.cpp b/src/sys_settings.cpp index 189657cb..c62b0f8a 100644 --- a/src/sys_settings.cpp +++ b/src/sys_settings.cpp @@ -1178,6 +1178,10 @@ string t_sys_settings::get_options_built(void) const { if (!options_built.empty()) options_built += ", "; options_built += "Speex"; #endif +#ifdef HAVE_OPUS + if (!options_built.empty()) options_built += ", "; + options_built += "Opus"; +#endif #ifdef HAVE_ILBC if (!options_built.empty()) options_built += ", "; options_built += "iLBC"; diff --git a/src/user.cpp b/src/user.cpp index 65685f15..0c7ddf79 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -82,6 +82,15 @@ extern t_phone *phone; #define FLD_SPEEX_DSP_AGC_LEVEL "speex_dsp_agc_level" #define FLD_SPEEX_DSP_AEC "speex_dsp_aec" #define FLD_SPEEX_DSP_NRD "speex_dsp_nrd" +#define FLD_OPUS_BANDWIDTH "opus_bandwidth" +#define FLD_OPUS_BITRATE "opus_bitrate" +#define FLD_OPUS_CBR "opus_cbr" +#define FLD_OPUS_COMPLEXITY "opus_complexity" +#define FLD_OPUS_DTX "opus_dtx" +#define FLD_OPUS_FEC "opus_fec" +#define FLD_OPUS_PACKET_LOSS "opus_packet_loss" +#define FLD_OPUS_PAYLOAD_TYPE "opus_payload_type" +#define FLD_OPUS_SIGNAL_TYPE "opus_signal_type" #define FLD_ILBC_PAYLOAD_TYPE "ilbc_payload_type" #define FLD_ILBC_MODE "ilbc_mode" #define FLD_G726_16_PAYLOAD_TYPE "g726_16_payload_type" @@ -248,6 +257,48 @@ string t_user::dtmf_transport2str(t_dtmf_transport d) const { return ""; } +#ifdef HAVE_OPUS +opus_int32 t_user::str2opus_bandwidth(const string &s) const { + if (s == "nb") return OPUS_BANDWIDTH_NARROWBAND; + if (s == "mb") return OPUS_BANDWIDTH_MEDIUMBAND; + if (s == "wb") return OPUS_BANDWIDTH_WIDEBAND; + if (s == "swb") return OPUS_BANDWIDTH_SUPERWIDEBAND; + if (s == "fb") return OPUS_BANDWIDTH_FULLBAND; + return OPUS_BANDWIDTH_FULLBAND; +} + +string t_user::opus_bandwidth2str(opus_int32 bandwidth) const { + switch (bandwidth) { + case OPUS_BANDWIDTH_NARROWBAND: return "nb"; + case OPUS_BANDWIDTH_MEDIUMBAND: return "mb"; + case OPUS_BANDWIDTH_WIDEBAND: return "wb"; + case OPUS_BANDWIDTH_SUPERWIDEBAND: return "swb"; + case OPUS_BANDWIDTH_FULLBAND: return "fb"; + default: + assert(false); + } + return ""; +} + +opus_int32 t_user::str2opus_signal_type(const string &s) const { + if (s == "auto") return OPUS_AUTO; + if (s == "voice") return OPUS_SIGNAL_VOICE; + if (s == "music") return OPUS_SIGNAL_MUSIC; + return OPUS_AUTO; +} + +string t_user::opus_signal_type2str(opus_int32 signal_type) const { + switch (signal_type) { + case OPUS_AUTO: return "auto"; + case OPUS_SIGNAL_VOICE: return "voice"; + case OPUS_SIGNAL_MUSIC: return "music"; + default: + assert(false); + } + return ""; +} +#endif + t_g726_packing t_user::str2g726_packing(const string &s) const { if (s == "rfc3551") return G726_PACK_RFC3551; if (s == "aal2") return G726_PACK_AAL2; @@ -366,6 +417,9 @@ t_user::t_user() { codecs.push_back(CODEC_SPEEX_WB); codecs.push_back(CODEC_SPEEX_NB); #endif +#ifdef HAVE_OPUS + codecs.push_back(CODEC_OPUS); +#endif #ifdef HAVE_ILBC codecs.push_back(CODEC_ILBC); #endif @@ -416,6 +470,17 @@ t_user::t_user() { speex_dsp_aec = false; speex_dsp_nrd = true; speex_dsp_agc_level = 20; +#ifdef HAVE_OPUS + opus_bandwidth = OPUS_BANDWIDTH_FULLBAND; + opus_bitrate = 0; + opus_cbr = false; + opus_complexity = 10; + opus_dtx = false; + opus_fec = false; + opus_packet_loss = 0; + opus_payload_type = 106; + opus_signal_type = OPUS_AUTO; +#endif ilbc_payload_type = 96; ilbc_mode = 30; g726_16_payload_type = 102; @@ -513,6 +578,17 @@ t_user::t_user(const t_user &u) { speex_dsp_agc_level = u.speex_dsp_agc_level; speex_dsp_aec = u.speex_dsp_aec; speex_dsp_nrd = u.speex_dsp_nrd; +#ifdef HAVE_OPUS + opus_bandwidth = u.opus_bandwidth; + opus_bitrate = u.opus_bitrate; + opus_cbr = u.opus_cbr; + opus_complexity = u.opus_complexity; + opus_dtx = u.opus_dtx; + opus_fec = u.opus_fec; + opus_packet_loss = u.opus_packet_loss; + opus_payload_type = u.opus_payload_type; + opus_signal_type = u.opus_signal_type; +#endif ilbc_payload_type = u.ilbc_payload_type; ilbc_mode = u.ilbc_mode; g726_16_payload_type = u.g726_16_payload_type; @@ -899,6 +975,80 @@ bool t_user::get_speex_dsp_nrd(void) const { return result; } +#ifdef HAVE_OPUS +opus_int32 t_user::get_opus_bandwidth(void) const { + opus_int32 result; + mtx_user.lock(); + result = opus_bandwidth; + mtx_user.unlock(); + return result; +} + +unsigned int t_user::get_opus_bitrate(void) const { + unsigned int result; + mtx_user.lock(); + result = opus_bitrate; + mtx_user.unlock(); + return result; +} + +bool t_user::get_opus_cbr(void) const { + bool result; + mtx_user.lock(); + result = opus_cbr; + mtx_user.unlock(); + return result; +} + +unsigned short t_user::get_opus_complexity(void) const { + unsigned short result; + mtx_user.lock(); + result = opus_complexity; + mtx_user.unlock(); + return result; +} + +bool t_user::get_opus_dtx(void) const { + bool result; + mtx_user.lock(); + result = opus_dtx; + mtx_user.unlock(); + return result; +} + +bool t_user::get_opus_fec(void) const { + bool result; + mtx_user.lock(); + result = opus_fec; + mtx_user.unlock(); + return 0 && result; +} + +unsigned short t_user::get_opus_packet_loss(void) const { + unsigned short result; + mtx_user.lock(); + result = opus_packet_loss; + mtx_user.unlock(); + return result; +} + +unsigned short t_user::get_opus_payload_type(void) const { + unsigned short result; + mtx_user.lock(); + result = opus_payload_type; + mtx_user.unlock(); + return result; +} + +opus_int32 t_user::get_opus_signal_type(void) const { + opus_int32 result; + mtx_user.lock(); + result = opus_signal_type; + mtx_user.unlock(); + return result; +} +#endif + unsigned short t_user::get_ilbc_payload_type(void) const { unsigned short result; mtx_user.lock(); @@ -1692,6 +1842,62 @@ void t_user::set_speex_dsp_nrd(bool b) { mtx_user.unlock(); } +#ifdef HAVE_OPUS +void t_user::set_opus_bandwidth(opus_int32 bandwidth) { + mtx_user.lock(); + opus_bandwidth = bandwidth; + mtx_user.unlock(); +} + +void t_user::set_opus_bitrate(unsigned int bitrate) { + mtx_user.lock(); + opus_bitrate = bitrate; + mtx_user.unlock(); +} + +void t_user::set_opus_cbr(bool b) { + mtx_user.lock(); + opus_cbr = b; + mtx_user.unlock(); +} + +void t_user::set_opus_complexity(unsigned short complexity) { + mtx_user.lock(); + opus_complexity = complexity; + mtx_user.unlock(); +} + +void t_user::set_opus_dtx(bool b) { + mtx_user.lock(); + opus_dtx = b; + mtx_user.unlock(); +} + +void t_user::set_opus_fec(bool b) { + mtx_user.lock(); + opus_fec = b; + mtx_user.unlock(); +} + +void t_user::set_opus_packet_loss(unsigned short packet_loss) { + mtx_user.lock(); + opus_packet_loss = packet_loss; + mtx_user.unlock(); +} + +void t_user::set_opus_payload_type(unsigned short payload_type) { + mtx_user.lock(); + opus_payload_type = payload_type; + mtx_user.unlock(); +} + +void t_user::set_opus_signal_type(opus_int32 signal_type) { + mtx_user.lock(); + opus_signal_type = signal_type; + mtx_user.unlock(); +} +#endif + void t_user::set_ilbc_payload_type(unsigned short payload_type) { mtx_user.lock(); ilbc_payload_type = payload_type; @@ -2263,6 +2469,10 @@ bool t_user::read_config(const string &filename, string &error_msg) { } else if (codec == "speex-uwb") { codecs.push_back(CODEC_SPEEX_UWB); #endif +#ifdef HAVE_OPUS + } else if (codec == "opus") { + codecs.push_back(CODEC_OPUS); +#endif #ifdef HAVE_ILBC } else if (codec == "ilbc") { codecs.push_back(CODEC_ILBC); @@ -2459,6 +2669,59 @@ bool t_user::read_config(const string &filename, string &error_msg) { mtx_user.unlock(); return false; } +#ifdef HAVE_OPUS + } else if (parameter == FLD_OPUS_BANDWIDTH) { + opus_bandwidth = str2opus_bandwidth(value); + } else if (parameter == FLD_OPUS_BITRATE) { + opus_bitrate = atoi(value.c_str()); + if ((opus_bitrate != 0) && (opus_bitrate < 500 || opus_bitrate > 512000)) { + error_msg = "Syntax error in file "; + error_msg += f; + error_msg += "\n"; + error_msg += "Invalid value for Opus bitrate: "; + error_msg += to_string(opus_bitrate); + log_file->write_report(error_msg, "t_user::read_config", + LOG_NORMAL, LOG_CRITICAL); + mtx_user.unlock(); + return false; + } + } else if (parameter == FLD_OPUS_CBR) { + opus_cbr = yesno2bool(value); + } else if (parameter == FLD_OPUS_COMPLEXITY) { + opus_complexity = atoi(value.c_str()); + if (opus_complexity > 10) { + error_msg = "Syntax error in file "; + error_msg += f; + error_msg += "\n"; + error_msg += "Invalid value for Opus complexity: "; + error_msg += value; + log_file->write_report(error_msg, "t_user::read_config", + LOG_NORMAL, LOG_CRITICAL); + mtx_user.unlock(); + return false; + } + } else if (parameter == FLD_OPUS_DTX) { + opus_dtx = yesno2bool(value); + } else if (parameter == FLD_OPUS_FEC) { + opus_fec = yesno2bool(value); + } else if (parameter == FLD_OPUS_PACKET_LOSS) { + opus_packet_loss = atoi(value.c_str()); + if (opus_packet_loss > 100) { + error_msg = "Syntax error in file "; + error_msg += f; + error_msg += "\n"; + error_msg += "Invalid value for Opus packet loss: "; + error_msg += value; + log_file->write_report(error_msg, "t_user::read_config", + LOG_NORMAL, LOG_CRITICAL); + mtx_user.unlock(); + return false; + } + } else if (parameter == FLD_OPUS_PAYLOAD_TYPE) { + opus_payload_type = atoi(value.c_str()); + } else if (parameter == FLD_OPUS_SIGNAL_TYPE) { + opus_signal_type = str2opus_signal_type(value); +#endif } else if (parameter == FLD_ILBC_PAYLOAD_TYPE) { ilbc_payload_type = atoi(value.c_str()); } else if (parameter == FLD_ILBC_MODE) { @@ -2685,6 +2948,9 @@ bool t_user::write_config(const string &filename, string &error_msg) { case CODEC_SPEEX_UWB: config << "speex-uwb"; break; + case CODEC_OPUS: + config << "opus"; + break; case CODEC_ILBC: config << "ilbc"; break; @@ -2730,6 +2996,17 @@ bool t_user::write_config(const string &filename, string &error_msg) { config << FLD_SPEEX_DSP_AEC << '=' << bool2yesno(speex_dsp_aec) << endl; config << FLD_SPEEX_DSP_NRD << '=' << bool2yesno(speex_dsp_nrd) << endl; config << FLD_SPEEX_DSP_AGC_LEVEL << '=' << speex_dsp_agc_level << endl; +#ifdef HAVE_OPUS + config << FLD_OPUS_BANDWIDTH << '=' << opus_bandwidth2str(opus_bandwidth) << endl; + config << FLD_OPUS_BITRATE << '=' << opus_bitrate << endl; + config << FLD_OPUS_CBR << '=' << bool2yesno(opus_cbr) << endl; + config << FLD_OPUS_COMPLEXITY << '=' << opus_complexity << endl; + config << FLD_OPUS_DTX << '=' << bool2yesno(opus_dtx) << endl; + config << FLD_OPUS_FEC << '=' << bool2yesno(opus_fec) << endl; + config << FLD_OPUS_PACKET_LOSS << '=' << opus_packet_loss << endl; + config << FLD_OPUS_PAYLOAD_TYPE << '=' << opus_payload_type << endl; + config << FLD_OPUS_SIGNAL_TYPE << '=' << opus_signal_type2str(opus_signal_type) << endl; +#endif config << FLD_ILBC_PAYLOAD_TYPE << '=' << ilbc_payload_type << endl; config << FLD_ILBC_MODE << '=' << ilbc_mode << endl; config << FLD_G726_16_PAYLOAD_TYPE << '=' << g726_16_payload_type << endl; diff --git a/src/user.h b/src/user.h index bbd33fae..63f4f9db 100644 --- a/src/user.h +++ b/src/user.h @@ -31,6 +31,10 @@ #include "threads/mutex.h" #include +#ifdef HAVE_OPUS +#include +#endif + // Forward declaration class t_request; @@ -211,6 +215,21 @@ class t_user { unsigned short speex_complexity; unsigned short speex_quality; // quality measure (worst 0-10 best) +#ifdef HAVE_OPUS + // RTP dynamic payload type for Opus + unsigned short opus_payload_type; + + // Opus options + opus_int32 opus_bandwidth; + unsigned int opus_bitrate; // in bps, 0=auto + bool opus_cbr; + unsigned short opus_complexity; + bool opus_dtx; + bool opus_fec; + unsigned short opus_packet_loss; + opus_int32 opus_signal_type; +#endif + // RTP dynamic payload types for iLBC unsigned short ilbc_payload_type; @@ -534,6 +553,12 @@ class t_user { string bit_rate_type2str(t_bit_rate_type b) const; t_dtmf_transport str2dtmf_transport(const string &s) const; string dtmf_transport2str(t_dtmf_transport d) const; +#ifdef HAVE_OPUS + opus_int32 str2opus_bandwidth(const string &s) const; + string opus_bandwidth2str(opus_int32 bandwidth) const; + opus_int32 str2opus_signal_type(const string &s) const; + string opus_signal_type2str(opus_int32 signal_type) const; +#endif t_g726_packing str2g726_packing(const string &s) const; string g726_packing2str(t_g726_packing packing) const; t_sip_transport str2sip_transport(const string &s) const; @@ -594,6 +619,17 @@ class t_user { bool get_speex_dsp_aec(void) const; bool get_speex_dsp_nrd(void) const; unsigned short get_speex_dsp_agc_level(void) const; +#ifdef HAVE_OPUS + unsigned int get_opus_bitrate(void) const; + opus_int32 get_opus_bandwidth(void) const; + bool get_opus_cbr(void) const; + unsigned short get_opus_complexity(void) const; + bool get_opus_dtx(void) const; + bool get_opus_fec(void) const; + unsigned short get_opus_packet_loss(void) const; + unsigned short get_opus_payload_type(void) const; + opus_int32 get_opus_signal_type(void) const; +#endif unsigned short get_ilbc_payload_type(void) const; unsigned short get_ilbc_mode(void) const; unsigned short get_g726_16_payload_type(void) const; @@ -713,6 +749,17 @@ class t_user { void set_speex_dsp_aec(bool b); void set_speex_dsp_nrd(bool b); void set_speex_dsp_agc_level(unsigned short level); +#ifdef HAVE_OPUS + void set_opus_bandwidth(opus_int32 bandwidth); + void set_opus_bitrate(unsigned int bitrate); + void set_opus_cbr(bool b); + void set_opus_complexity(unsigned short complexity); + void set_opus_dtx(bool b); + void set_opus_fec(bool b); + void set_opus_packet_loss(unsigned short packet_loss); + void set_opus_payload_type(unsigned short payload_type); + void set_opus_signal_type(opus_int32 signal_type); +#endif void set_ilbc_payload_type(unsigned short payload_type); void set_g726_16_payload_type(unsigned short payload_type); void set_g726_24_payload_type(unsigned short payload_type); diff --git a/src/userintf.cpp b/src/userintf.cpp index 03cb6a6b..f4759c28 100644 --- a/src/userintf.cpp +++ b/src/userintf.cpp @@ -2192,6 +2192,7 @@ string t_userintf::format_codec(t_audio_codec codec) const { case CODEC_SPEEX_NB: return "spx-nb"; case CODEC_SPEEX_WB: return "spx-wb"; case CODEC_SPEEX_UWB: return "spx-uwb"; + case CODEC_OPUS: return "opus"; case CODEC_ILBC: return "ilbc"; case CODEC_G722: return "g722"; case CODEC_G726_16: return "g726-16"; diff --git a/twinkle_config.h.in b/twinkle_config.h.in index 53a04267..f614f4b9 100644 --- a/twinkle_config.h.in +++ b/twinkle_config.h.in @@ -1,5 +1,6 @@ #cmakedefine WITH_DIAMONDCARD #cmakedefine HAVE_SPEEX +#cmakedefine HAVE_OPUS #cmakedefine HAVE_ILBC #cmakedefine HAVE_ILBC_CPP #cmakedefine HAVE_ZRTP