├── .gitignore ├── CMakeLists.txt ├── idf_component.yml ├── include ├── opus_resampler.h ├── opus_decoder.h ├── opus_encoder.h └── resampler_structs.h ├── opus_resampler.cc ├── silk_resampler.h ├── opus_decoder.cc └── opus_encoder.cc /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS 3 | "opus_encoder.cc" 4 | "opus_decoder.cc" 5 | "opus_resampler.cc" 6 | INCLUDE_DIRS 7 | "include" 8 | PRIV_INCLUDE_DIRS 9 | "." 10 | ) -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: ">=5.3" 3 | 78/esp-opus: "^1.0.5" 4 | description: ESP32 Opus Encoder C++ wrapper 5 | files: 6 | exclude: 7 | - .git 8 | license: MIT 9 | repository: https://github.com/78/esp-opus-encoder 10 | url: https://github.com/78/esp-opus-encoder 11 | version: 2.4.1 -------------------------------------------------------------------------------- /include/opus_resampler.h: -------------------------------------------------------------------------------- 1 | #ifndef OPUS_RESAMPLER_H 2 | #define OPUS_RESAMPLER_H 3 | 4 | #include 5 | #include "opus.h" 6 | #include "resampler_structs.h" 7 | 8 | class OpusResampler { 9 | public: 10 | OpusResampler(); 11 | ~OpusResampler(); 12 | 13 | void Configure(int input_sample_rate, int output_sample_rate); 14 | void Process(const int16_t *input, int input_samples, int16_t *output); 15 | int GetOutputSamples(int input_samples) const; 16 | 17 | int input_sample_rate() const { return input_sample_rate_; } 18 | int output_sample_rate() const { return output_sample_rate_; } 19 | 20 | private: 21 | silk_resampler_state_struct resampler_state_; 22 | int input_sample_rate_; 23 | int output_sample_rate_; 24 | }; 25 | 26 | #endif 27 | 28 | 29 | -------------------------------------------------------------------------------- /include/opus_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef _OPUS_DECODER_WRAPPER_H_ 2 | #define _OPUS_DECODER_WRAPPER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "opus.h" 10 | 11 | 12 | class OpusDecoderWrapper { 13 | public: 14 | OpusDecoderWrapper(int sample_rate, int channels, int duration_ms = 60); 15 | ~OpusDecoderWrapper(); 16 | 17 | bool Decode(std::vector&& opus, std::vector& pcm); 18 | void ResetState(); 19 | 20 | inline int sample_rate() const { 21 | return sample_rate_; 22 | } 23 | 24 | inline int duration_ms() const { 25 | return duration_ms_; 26 | } 27 | 28 | private: 29 | std::mutex mutex_; 30 | struct OpusDecoder* audio_dec_ = nullptr; 31 | int frame_size_; 32 | int sample_rate_; 33 | int duration_ms_; 34 | }; 35 | 36 | #endif // _OPUS_DECODER_WRAPPER_H_ 37 | -------------------------------------------------------------------------------- /include/opus_encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef _OPUS_ENCODER_WRAPPER_H_ 2 | #define _OPUS_ENCODER_WRAPPER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "opus.h" 11 | 12 | #define MAX_OPUS_PACKET_SIZE 1000 13 | 14 | 15 | class OpusEncoderWrapper { 16 | public: 17 | OpusEncoderWrapper(int sample_rate, int channels, int duration_ms = 60); 18 | ~OpusEncoderWrapper(); 19 | 20 | inline int sample_rate() const { 21 | return sample_rate_; 22 | } 23 | 24 | inline int duration_ms() const { 25 | return duration_ms_; 26 | } 27 | 28 | void SetDtx(bool enable); 29 | void SetComplexity(int complexity); 30 | bool Encode(std::vector&& pcm, std::vector& opus); 31 | void Encode(std::vector&& pcm, std::function&& opus)> handler); 32 | bool IsBufferEmpty() const { return in_buffer_.empty(); } 33 | void ResetState(); 34 | 35 | private: 36 | std::mutex mutex_; 37 | struct OpusEncoder* audio_enc_ = nullptr; 38 | int sample_rate_; 39 | int duration_ms_; 40 | int frame_size_; 41 | std::vector in_buffer_; 42 | }; 43 | 44 | #endif // _OPUS_ENCODER_H_ 45 | -------------------------------------------------------------------------------- /opus_resampler.cc: -------------------------------------------------------------------------------- 1 | #include "opus_resampler.h" 2 | #include "silk_resampler.h" 3 | #include "esp_log.h" 4 | 5 | #define TAG "OpusResampler" 6 | 7 | OpusResampler::OpusResampler() { 8 | } 9 | 10 | OpusResampler::~OpusResampler() { 11 | } 12 | 13 | void OpusResampler::Configure(int input_sample_rate, int output_sample_rate) { 14 | int encode = input_sample_rate > output_sample_rate ? 1 : 0; 15 | auto ret = silk_resampler_init(&resampler_state_, input_sample_rate, output_sample_rate, encode); 16 | if (ret != 0) { 17 | ESP_LOGE(TAG, "Failed to initialize resampler"); 18 | return; 19 | } 20 | input_sample_rate_ = input_sample_rate; 21 | output_sample_rate_ = output_sample_rate; 22 | ESP_LOGI(TAG, "Resampler configured with input sample rate %d and output sample rate %d", input_sample_rate_, output_sample_rate_); 23 | } 24 | 25 | void OpusResampler::Process(const int16_t *input, int input_samples, int16_t *output) { 26 | auto ret = silk_resampler(&resampler_state_, output, input, input_samples); 27 | if (ret != 0) { 28 | ESP_LOGE(TAG, "Failed to process resampler"); 29 | } 30 | } 31 | 32 | int OpusResampler::GetOutputSamples(int input_samples) const { 33 | return input_samples * output_sample_rate_ / input_sample_rate_; 34 | } 35 | -------------------------------------------------------------------------------- /silk_resampler.h: -------------------------------------------------------------------------------- 1 | #ifndef _SILK_RESAMPLER_H_ 2 | #define _SILK_RESAMPLER_H_ 3 | 4 | #include "opus.h" 5 | #include "resampler_structs.h" 6 | /*! 7 | * Initialize/reset the resampler state for a given pair of input/output sampling rates 8 | */ 9 | extern "C" opus_int silk_resampler_init( 10 | silk_resampler_state_struct *S, /* I/O Resampler state */ 11 | opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */ 12 | opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */ 13 | opus_int forEnc /* I If 1: encoder; if 0: decoder */ 14 | ); 15 | 16 | /*! 17 | * Resampler: convert from one sampling rate to another 18 | */ 19 | extern "C" opus_int silk_resampler( 20 | silk_resampler_state_struct *S, /* I/O Resampler state */ 21 | opus_int16 out[], /* O Output signal */ 22 | const opus_int16 in[], /* I Input signal */ 23 | opus_int32 inLen /* I Number of input samples */ 24 | ); 25 | 26 | #endif // _SILK_RESAMPLER_H_ 27 | -------------------------------------------------------------------------------- /opus_decoder.cc: -------------------------------------------------------------------------------- 1 | #include "opus_decoder.h" 2 | #include 3 | 4 | #define TAG "OpusDecoderWrapper" 5 | 6 | OpusDecoderWrapper::OpusDecoderWrapper(int sample_rate, int channels, int duration_ms) 7 | : sample_rate_(sample_rate), duration_ms_(duration_ms) { 8 | int error; 9 | audio_dec_ = opus_decoder_create(sample_rate, channels, &error); 10 | if (audio_dec_ == nullptr) { 11 | ESP_LOGE(TAG, "Failed to create audio decoder, error code: %d", error); 12 | return; 13 | } 14 | 15 | frame_size_ = sample_rate / 1000 * channels * duration_ms; 16 | } 17 | 18 | OpusDecoderWrapper::~OpusDecoderWrapper() { 19 | std::lock_guard lock(mutex_); 20 | if (audio_dec_ != nullptr) { 21 | opus_decoder_destroy(audio_dec_); 22 | } 23 | } 24 | 25 | bool OpusDecoderWrapper::Decode(std::vector&& opus, std::vector& pcm) { 26 | std::lock_guard lock(mutex_); 27 | if (audio_dec_ == nullptr) { 28 | ESP_LOGE(TAG, "Audio decoder is not configured"); 29 | return false; 30 | } 31 | 32 | pcm.resize(frame_size_); 33 | auto ret = opus_decode(audio_dec_, opus.data(), opus.size(), pcm.data(), pcm.size(), 0); 34 | if (ret < 0) { 35 | ESP_LOGE(TAG, "Failed to decode audio, error code: %d", ret); 36 | return false; 37 | } 38 | 39 | // Resize the pcm vector to the actual decoded samples 40 | pcm.resize(ret); 41 | return true; 42 | } 43 | 44 | void OpusDecoderWrapper::ResetState() { 45 | std::lock_guard lock(mutex_); 46 | if (audio_dec_ != nullptr) { 47 | opus_decoder_ctl(audio_dec_, OPUS_RESET_STATE); 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /include/resampler_structs.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | Copyright (c) 2006-2011, Skype Limited. All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | - Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | - Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | - Neither the name of Internet Society, IETF or IETF Trust, nor the 12 | names of specific contributors, may be used to endorse or promote 13 | products derived from this software without specific prior written 14 | permission. 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | ***********************************************************************/ 27 | 28 | #ifndef SILK_RESAMPLER_STRUCTS_H 29 | #define SILK_RESAMPLER_STRUCTS_H 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | #define SILK_RESAMPLER_MAX_FIR_ORDER 36 36 | #define SILK_RESAMPLER_MAX_IIR_ORDER 6 37 | 38 | typedef struct _silk_resampler_state_struct{ 39 | opus_int32 sIIR[ SILK_RESAMPLER_MAX_IIR_ORDER ]; /* this must be the first element of this struct */ 40 | union{ 41 | opus_int32 i32[ SILK_RESAMPLER_MAX_FIR_ORDER ]; 42 | opus_int16 i16[ SILK_RESAMPLER_MAX_FIR_ORDER ]; 43 | } sFIR; 44 | opus_int16 delayBuf[ 48 ]; 45 | opus_int resampler_function; 46 | opus_int batchSize; 47 | opus_int32 invRatio_Q16; 48 | opus_int FIR_Order; 49 | opus_int FIR_Fracs; 50 | opus_int Fs_in_kHz; 51 | opus_int Fs_out_kHz; 52 | opus_int inputDelay; 53 | const opus_int16 *Coefs; 54 | } silk_resampler_state_struct; 55 | 56 | #ifdef __cplusplus 57 | } 58 | #endif 59 | #endif /* SILK_RESAMPLER_STRUCTS_H */ 60 | 61 | -------------------------------------------------------------------------------- /opus_encoder.cc: -------------------------------------------------------------------------------- 1 | #include "opus_encoder.h" 2 | #include 3 | 4 | #define TAG "OpusEncoderWrapper" 5 | 6 | OpusEncoderWrapper::OpusEncoderWrapper(int sample_rate, int channels, int duration_ms) 7 | : sample_rate_(sample_rate), duration_ms_(duration_ms) { 8 | int error; 9 | audio_enc_ = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error); 10 | if (audio_enc_ == nullptr) { 11 | ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", error); 12 | return; 13 | } 14 | 15 | // Default DTX enabled 16 | SetDtx(true); 17 | // Complexity 5 almost uses up all CPU of ESP32C3 while complexity 0 uses the least 18 | SetComplexity(0); 19 | 20 | frame_size_ = sample_rate / 1000 * channels * duration_ms; 21 | } 22 | 23 | OpusEncoderWrapper::~OpusEncoderWrapper() { 24 | std::lock_guard lock(mutex_); 25 | if (audio_enc_ != nullptr) { 26 | opus_encoder_destroy(audio_enc_); 27 | } 28 | } 29 | 30 | void OpusEncoderWrapper::Encode(std::vector&& pcm, std::function&& opus)> handler) { 31 | std::lock_guard lock(mutex_); 32 | if (audio_enc_ == nullptr) { 33 | ESP_LOGE(TAG, "Audio encoder is not configured"); 34 | return; 35 | } 36 | 37 | if (in_buffer_.empty()) { 38 | in_buffer_ = std::move(pcm); 39 | } else { 40 | /* ISSUE: https://github.com/78/esp-opus-encoder/issues/1 */ 41 | in_buffer_.reserve(in_buffer_.size() + pcm.size()); 42 | in_buffer_.insert(in_buffer_.end(), pcm.begin(), pcm.end()); 43 | } 44 | 45 | while (in_buffer_.size() >= frame_size_) { 46 | uint8_t opus[MAX_OPUS_PACKET_SIZE]; 47 | auto ret = opus_encode(audio_enc_, in_buffer_.data(), frame_size_, opus, MAX_OPUS_PACKET_SIZE); 48 | if (ret < 0) { 49 | ESP_LOGE(TAG, "Failed to encode audio, error code: %ld", ret); 50 | return; 51 | } 52 | 53 | if (handler != nullptr) { 54 | handler(std::vector(opus, opus + ret)); 55 | } 56 | 57 | in_buffer_.erase(in_buffer_.begin(), in_buffer_.begin() + frame_size_); 58 | } 59 | } 60 | 61 | bool OpusEncoderWrapper::Encode(std::vector&& pcm, std::vector& opus) { 62 | if (audio_enc_ == nullptr) { 63 | ESP_LOGE(TAG, "Audio encoder is not configured"); 64 | return false; 65 | } 66 | 67 | if (pcm.size() != frame_size_) { 68 | ESP_LOGE(TAG, "Audio data size is not equal to frame size, size: %u, frame size: %u", pcm.size(), frame_size_); 69 | return false; 70 | } 71 | 72 | uint8_t buf[MAX_OPUS_PACKET_SIZE]; 73 | auto ret = opus_encode(audio_enc_, pcm.data(), frame_size_, buf, MAX_OPUS_PACKET_SIZE); 74 | if (ret < 0) { 75 | ESP_LOGE(TAG, "Failed to encode audio, error code: %ld", ret); 76 | return false; 77 | } 78 | opus.assign(buf, buf + ret); 79 | return true; 80 | } 81 | 82 | void OpusEncoderWrapper::ResetState() { 83 | std::lock_guard lock(mutex_); 84 | if (audio_enc_ != nullptr) { 85 | opus_encoder_ctl(audio_enc_, OPUS_RESET_STATE); 86 | in_buffer_.clear(); 87 | } 88 | } 89 | 90 | void OpusEncoderWrapper::SetDtx(bool enable) { 91 | std::lock_guard lock(mutex_); 92 | if (audio_enc_ != nullptr) { 93 | opus_encoder_ctl(audio_enc_, OPUS_SET_DTX(enable ? 1 : 0)); 94 | } 95 | } 96 | 97 | void OpusEncoderWrapper::SetComplexity(int complexity) { 98 | std::lock_guard lock(mutex_); 99 | if (audio_enc_ != nullptr) { 100 | opus_encoder_ctl(audio_enc_, OPUS_SET_COMPLEXITY(complexity)); 101 | } 102 | } 103 | --------------------------------------------------------------------------------