From ed6a75be3177d224f5fafe19400074c7267a6048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Thu, 19 Dec 2019 18:31:18 -0500 Subject: [PATCH 1/7] Extract the (optional) number of channels from the rtpmap codec name --- src/sdp/sdp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sdp/sdp.cpp b/src/sdp/sdp.cpp index d8a94ea9..59ffc117 100644 --- a/src/sdp/sdp.cpp +++ b/src/sdp/sdp.cpp @@ -640,6 +640,10 @@ t_audio_codec t_sdp::get_rtpmap_codec(const string &rtpmap) const { string codec_name = trim(rtpmap_elems[0]); int sample_rate = atoi(trim(rtpmap_elems[1]).c_str()); + int channels = 1; + if (rtpmap_elems.size() >= 3) { + channels = atoi(trim(rtpmap_elems[2]).c_str()); + } if (cmp_nocase(codec_name, SDP_AC_NAME_G711_ULAW) == 0 && sample_rate == 8000) { return CODEC_G711_ULAW; From a108d7410a5080857a85d1aab9381883c23f360c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Thu, 19 Dec 2019 20:17:27 -0500 Subject: [PATCH 2/7] Reconstruct the parameters string in t_sdp::get_fmtp() t_sdp::get_fmtp() previously assumed that any "a=fmtp:" line contained only two tokens, but RFC 4566 does not preclude the presence of spaces in the "format specific parameters" part. In fact, there are many examples (such as found in RFC 6871) where multiple parameters are separated by "; ", thus creating several tokens. The proper fix would probably be to introduce a `maxsplit` argument to split_ws() in order to keep the whole string intact, but that would be a bit intrusive. Simply putting some spaces back to reconstruct the string to a somewhat equivalent version will be enough for our needs. --- src/sdp/sdp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sdp/sdp.cpp b/src/sdp/sdp.cpp index 59ffc117..1ebb5dcf 100644 --- a/src/sdp/sdp.cpp +++ b/src/sdp/sdp.cpp @@ -722,7 +722,9 @@ string t_sdp::get_fmtp(t_sdp_media_type media_type, unsigned short codec) const { vector l = split_ws((*i)->value); if (atoi(l.front().c_str()) == codec) { - return l.back(); + // Reconstruct the fmtp parameters string + vector params((l.begin() + 1), l.end()); + return join_strings(params, " "); } } From 4289d9174a0a77f9156748eaab64107d7440e093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Thu, 19 Dec 2019 21:33:07 -0500 Subject: [PATCH 3/7] Add t_sdp::set_fmtp_int_params(), to add several fmtp params at once --- src/sdp/sdp.cpp | 20 ++++++++++++++++++++ src/sdp/sdp.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/src/sdp/sdp.cpp b/src/sdp/sdp.cpp index 1ebb5dcf..55e18f86 100644 --- a/src/sdp/sdp.cpp +++ b/src/sdp/sdp.cpp @@ -803,6 +803,26 @@ void t_sdp::set_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec set_fmtp(media_type, codec, fmtp); } +void t_sdp::set_fmtp_int_params(t_sdp_media_type media_type, unsigned short codec, + const std::map ¶ms) +{ + if (params.empty()) return; + + std::string fmtp; + bool first_param = true; + + for (const std::pair ¶m : params) { + fmtp += (first_param ? "" : "; "); + first_param = false; + + fmtp += param.first; + fmtp += '='; + fmtp += int2str(param.second); + } + + set_fmtp(media_type, codec, fmtp); +} + void t_sdp::set_zrtp_support(t_sdp_media_type media_type) { t_sdp_media *m = const_cast(get_first_media(media_type)); assert(m != NULL); diff --git a/src/sdp/sdp.h b/src/sdp/sdp.h index b9465b17..fc0881ad 100644 --- a/src/sdp/sdp.h +++ b/src/sdp/sdp.h @@ -282,6 +282,8 @@ class t_sdp : public t_sip_body { void set_fmtp(t_sdp_media_type media_type, unsigned short codec, const string &fmtp); void set_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec, const string ¶m, int value); + void set_fmtp_int_params(t_sdp_media_type media_type, unsigned short codec, + const std::map ¶ms); void set_zrtp_support(t_sdp_media_type media_type); // Returns a pointer to the first media stream in the list of media From 5515b8cce51f33b4307d4d1f420d6b3f52b328ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Fri, 20 Dec 2019 20:50:52 -0500 Subject: [PATCH 4/7] Make SDP format parameters available to any encoder/decoder --- src/audio/audio_codecs.h | 5 +++++ src/audio/audio_session.cpp | 4 ++++ src/audio/audio_session.h | 2 ++ src/session.h | 2 ++ 4 files changed, 13 insertions(+) diff --git a/src/audio/audio_codecs.h b/src/audio/audio_codecs.h index b4043b53..ba158064 100644 --- a/src/audio/audio_codecs.h +++ b/src/audio/audio_codecs.h @@ -41,6 +41,11 @@ enum t_audio_codec { CODEC_G729A }; +// 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 { +}; + // Default ptime values (ms) for audio codecs #define PTIME_G711_ALAW 20 #define PTIME_G711_ULAW 20 diff --git a/src/audio/audio_session.cpp b/src/audio/audio_session.cpp index 11ef8918..8326eaac 100644 --- a/src/audio/audio_session.cpp +++ b/src/audio/audio_session.cpp @@ -646,6 +646,10 @@ void t_audio_session::set_srtp_cipher_mode(const string &cipher_mode) { mtx_zrtp_data.unlock(); } +t_codec_sdp_params t_audio_session::get_codec_sdp_params(void) const { + // Returning by copy, since *session is not guaranteed to be stable + return session->codec_sdp_params; +} #ifdef HAVE_SPEEX bool t_audio_session::get_do_echo_cancellation(void) const { diff --git a/src/audio/audio_session.h b/src/audio/audio_session.h index 1e01ce01..4da37491 100644 --- a/src/audio/audio_session.h +++ b/src/audio/audio_session.h @@ -164,6 +164,8 @@ class t_audio_session { void set_zrtp_sas_confirmed(bool confirmed); void set_srtp_cipher_mode(const string &cipher_mode); + t_codec_sdp_params get_codec_sdp_params(void) const; + #ifdef HAVE_SPEEX // speex acoustic echo cancellation (AEC) manipulations bool get_do_echo_cancellation(void) const; diff --git a/src/session.h b/src/session.h index a467677e..b61607f0 100644 --- a/src/session.h +++ b/src/session.h @@ -112,6 +112,8 @@ class t_session { unsigned short send_dtmf_pt; // payload type for DTMF sending t_sdp recvd_sdp_offer; + t_codec_sdp_params codec_sdp_params; // SDP format parameters + t_session(t_dialog *_dialog, string _receive_host, unsigned short _receive_port); From b00f847a47059299c6b3f881d571e9b4c70e6122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Sat, 21 Dec 2019 22:06:49 -0500 Subject: [PATCH 5/7] Add support for the Opus codec Closes #101 --- .github/workflows/main.yml | 3 + CMakeLists.txt | 8 + README.md | 2 + src/CMakeLists.txt | 3 + src/audio/audio_codecs.cpp | 39 ++++ src/audio/audio_codecs.h | 32 ++++ src/audio/audio_decoder.cpp | 74 ++++++++ src/audio/audio_decoder.h | 23 +++ src/audio/audio_encoder.cpp | 226 ++++++++++++++++++++++ src/audio/audio_encoder.h | 19 ++ src/audio/audio_rx.cpp | 6 + src/audio/audio_tx.cpp | 4 + src/gui/userprofileform.cpp | 140 +++++++++++++- src/gui/userprofileform.ui | 363 +++++++++++++++++++++++++++++++++++- src/sdp/sdp.cpp | 5 + src/sdp/sdp.h | 2 + src/session.cpp | 125 ++++++++++++- src/session.h | 4 + src/sys_settings.cpp | 4 + src/user.cpp | 277 +++++++++++++++++++++++++++ src/user.h | 47 +++++ src/userintf.cpp | 1 + twinkle_config.h.in | 1 + 23 files changed, 1401 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8641b5d6..1be7b85f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,6 +10,7 @@ env: -DWITH_ALSA=ON -DWITH_DIAMONDCARD=ON -DWITH_GSM=ON + -DWITH_OPUS=ON -DWITH_SPEEX=ON -DWITH_ZRTP=OFF # Essential packages required by all builds @@ -19,6 +20,7 @@ env: flex libccrtp-dev libmagic-dev + libopus-dev libreadline-dev libsndfile1-dev libucommon-dev @@ -78,6 +80,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 8658c475..04803453 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) CMAKE_DEPENDENT_OPTION(WITH_DIAMONDCARD "Enable Diamondcard integration" ON "WITH_QT5" OFF) @@ -40,6 +41,8 @@ include (CMakePushCheckState) include (CheckCXXSourceCompiles) include (TestBigEndian) +find_package(PkgConfig REQUIRED) + find_package(LibXml2 REQUIRED) find_package(LibMagic REQUIRED) find_package(LibSndfile REQUIRED) @@ -110,6 +113,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 cf66718e..1ebcef90 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The following tools are also required: * libzrtpcpp (version >= 0.9.0) [ZRTP library, ccRTP support must be enabled](https://www.gnu.org/software/ccrtp/zrtp.html) * 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/) * [Akonadi](https://community.kde.org/KDE_PIM/Akonadi) (specifically, the `core` library) and [KContacts](https://cgit.kde.org/kcontacts.git) @@ -47,6 +48,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` * Akonadi support: `-DWITH_AKONADI=On` (requires `WITH_QT5`) 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 0cfe5b42..a24c0240 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 2ff05de4..08e78a32 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" @@ -122,6 +123,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(); @@ -139,6 +143,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; @@ -357,6 +364,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; } @@ -427,9 +438,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) { @@ -455,7 +506,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()) { @@ -476,10 +527,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); @@ -590,13 +650,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)-> @@ -616,6 +683,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) { @@ -663,6 +778,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 b61607f0..a1e993ce 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 5f7f5322..1b3adade 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 a8abc05c..678be473 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 4490d9eb..3cd9b35d 100644 --- a/src/userintf.cpp +++ b/src/userintf.cpp @@ -2229,6 +2229,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 dc4108b2..ce4d7fe2 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 From 60b927427f3cd2c82856deaf166d5b01ecd905d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Thu, 3 Mar 2022 21:34:40 -0500 Subject: [PATCH 6/7] Don't use `IMPORTED_TARGET` for `pkg_check_modules()` in CMakeLists.txt `IMPORTED_TARGET` was only introduced in CMake 3.6. --- CMakeLists.txt | 3 ++- src/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04803453..1ac1766e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,8 +114,9 @@ if (WITH_SPEEX) endif (WITH_SPEEX) if (WITH_OPUS) - pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus) + pkg_check_modules(Opus REQUIRED opus) set(HAVE_OPUS TRUE) + include_directories(${Opus_INCLUDE_DIRS}) endif (WITH_OPUS) if (WITH_ILBC) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 23d5759d..694d08a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,7 +101,7 @@ if (WITH_GSM) list(APPEND twinkle_LIBS ${GSM_LIBRARY}) endif (WITH_GSM) if (WITH_OPUS) - list(APPEND twinkle_LIBS PkgConfig::Opus) + list(APPEND twinkle_LIBS ${Opus_LIBRARIES}) endif (WITH_OPUS) if (WITH_QT5) From 968db573d03c3f84136fcada619ce1c9f0bf4408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Thu, 3 Mar 2022 23:28:22 -0500 Subject: [PATCH 7/7] Remove the name of all `` and `` elements These generic names added by Qt Designer do nothing but cause conflicts. --- src/gui/userprofileform.ui | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gui/userprofileform.ui b/src/gui/userprofileform.ui index a24c0240..be6e3ba5 100644 --- a/src/gui/userprofileform.ui +++ b/src/gui/userprofileform.ui @@ -1590,23 +1590,23 @@ If you disable this option, then the first codec from the active codecs that is Opus - + Opus - + - + Common maximum parameters - + - + @@ -1668,7 +1668,7 @@ If you disable this option, then the first codec from the active codecs that is - + @@ -1714,15 +1714,15 @@ If you disable this option, then the first codec from the active codecs that is - + Local parameters - + - + @@ -1752,7 +1752,7 @@ If you disable this option, then the first codec from the active codecs that is - + @@ -1824,7 +1824,7 @@ If you disable this option, then the first codec from the active codecs that is Remote parameters - + @@ -1864,7 +1864,7 @@ If you disable this option, then the first codec from the active codecs that is - + @@ -1896,7 +1896,7 @@ If you disable this option, then the first codec from the active codecs that is - + Qt::Horizontal @@ -1912,7 +1912,7 @@ If you disable this option, then the first codec from the active codecs that is - + Qt::Vertical