From e44c5b1cc73c274513f1090133e08dec69115caf Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 21 Nov 2023 22:30:25 -0800 Subject: [PATCH] Add opus codec test --- src/media/audio/decode/opus_decoder.cpp | 0 src/media/audio/decode/opus_decoder.h | 0 src/media/audio/encode/opus_encoder.cpp | 0 src/media/audio/encode/opus_encoder.h | 0 tests/opus/OpusDecoderImpl.cpp | 39 ++++++++ tests/opus/OpusDecoderImpl.h | 28 ++++++ tests/opus/OpusEncoderImpl.cpp | 116 ++++++++++++++++++++++++ tests/opus/OpusEncoderImpl.h | 33 +++++++ tests/opus/base_type.h | 10 ++ tests/opus/main.cpp | 31 +++++++ tests/opus/opus_test.cpp | 96 ++++++++++++++++++++ xmake.lua | 17 +++- 12 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 src/media/audio/decode/opus_decoder.cpp create mode 100644 src/media/audio/decode/opus_decoder.h create mode 100644 src/media/audio/encode/opus_encoder.cpp create mode 100644 src/media/audio/encode/opus_encoder.h create mode 100644 tests/opus/OpusDecoderImpl.cpp create mode 100644 tests/opus/OpusDecoderImpl.h create mode 100644 tests/opus/OpusEncoderImpl.cpp create mode 100644 tests/opus/OpusEncoderImpl.h create mode 100644 tests/opus/base_type.h create mode 100644 tests/opus/main.cpp create mode 100644 tests/opus/opus_test.cpp diff --git a/src/media/audio/decode/opus_decoder.cpp b/src/media/audio/decode/opus_decoder.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/media/audio/decode/opus_decoder.h b/src/media/audio/decode/opus_decoder.h new file mode 100644 index 0000000..e69de29 diff --git a/src/media/audio/encode/opus_encoder.cpp b/src/media/audio/encode/opus_encoder.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/media/audio/encode/opus_encoder.h b/src/media/audio/encode/opus_encoder.h new file mode 100644 index 0000000..e69de29 diff --git a/tests/opus/OpusDecoderImpl.cpp b/tests/opus/OpusDecoderImpl.cpp new file mode 100644 index 0000000..31e4f3e --- /dev/null +++ b/tests/opus/OpusDecoderImpl.cpp @@ -0,0 +1,39 @@ +#include "OpusDecoderImpl.h" +#define MAX_FRAME_SIZE 6 * 960 +#define CHANNELS 2 + +OpusDecoderImpl::OpusDecoderImpl(int sampleRate, int channel) { + int err; + decoder = opus_decoder_create(sampleRate, channel, &err); + opus_decoder_ctl(decoder, OPUS_SET_LSB_DEPTH(16)); + sample_rate = sample_rate; + channel_num = channel; + if (err < 0 || decoder == NULL) { + printf("创建解码器失败\n"); + return; + } + + pcm_file = fopen("decode.pcm", "wb+"); +} + +bool OpusDecoderImpl::Decode(unsigned char* in_data, int len) { + unsigned char pcm_bytes[MAX_FRAME_SIZE * CHANNELS * 2]; + opus_int16 out[MAX_FRAME_SIZE * CHANNELS]; + auto frame_size = opus_decode(decoder, in_data, len, out, MAX_FRAME_SIZE, 0); + + if (frame_size < 0) { + printf("解码失败\n"); + return false; + } + + for (auto i = 0; i < channel_num * frame_size; i++) { + pcm_bytes[2 * i] = out[i] & 0xFF; + pcm_bytes[2 * i + 1] = (out[i] >> 8) & 0xFF; + } + + fwrite(pcm_bytes, sizeof(short), frame_size * channel_num, pcm_file); + fflush(pcm_file); + return true; +} + +OpusDecoderImpl::~OpusDecoderImpl() {} \ No newline at end of file diff --git a/tests/opus/OpusDecoderImpl.h b/tests/opus/OpusDecoderImpl.h new file mode 100644 index 0000000..17cd9f2 --- /dev/null +++ b/tests/opus/OpusDecoderImpl.h @@ -0,0 +1,28 @@ + +#ifndef __OPUSDECODERIMPL_H +#define __OPUSDECODERIMPL_H +#include + +#include +#include +#include +#include + +#include "base_type.h" +#include "opus/opus.h" + +class OpusDecoderImpl { + private: + /* data */ + OpusDecoder *decoder; + int sample_rate; + int channel_num; + FILE *pcm_file; + + public: + bool Decode(unsigned char *in_data, int len); + OpusDecoderImpl(int sampleRate, int channel); + ~OpusDecoderImpl(); +}; + +#endif \ No newline at end of file diff --git a/tests/opus/OpusEncoderImpl.cpp b/tests/opus/OpusEncoderImpl.cpp new file mode 100644 index 0000000..ae94330 --- /dev/null +++ b/tests/opus/OpusEncoderImpl.cpp @@ -0,0 +1,116 @@ +#include "OpusEncoderImpl.h" + +#include +#include + +#include + +#include "OpusDecoderImpl.h" +#define MAX_PACKET_SIZE 3 * 1276 + +OpusEncoderImpl::OpusEncoderImpl(int sampleRate, int channel) + : channel_num(channel), sample_rate(sampleRate) { + int err; + int applications[3] = {OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_VOIP, + OPUS_APPLICATION_RESTRICTED_LOWDELAY}; + + encoder = opus_encoder_create(sampleRate, channel_num, applications[1], &err); + + if (err != OPUS_OK || encoder == NULL) { + printf("打开opus 编码器失败\n"); + } + + opus_encoder_ctl(encoder, OPUS_SET_VBR(0)); // 0:CBR, 1:VBR + opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(true)); + opus_encoder_ctl(encoder, OPUS_SET_BITRATE(96000)); + opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8)); // 8 0~10 + opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + opus_encoder_ctl(encoder, + OPUS_SET_LSB_DEPTH(16)); // 每个采样16个bit,2个byte + opus_encoder_ctl(encoder, OPUS_SET_DTX(0)); + opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0)); + + EncodeRun(); +} + +// every pcm frame takes 23ms +void OpusEncoderImpl::Feed(unsigned char *data, int len) { + mutex.lock(); + for (auto i = 0; i < len; i++) { + pcm_queue.emplace(data[i]); + } + mutex.unlock(); +} + +bool OpusEncoderImpl::PopFrame(StreamInfo &info) { + if (info_queue.size() > 0) { + access_mutex.lock(); + info = info_queue.front(); + info_queue.pop(); + access_mutex.unlock(); + return true; + } + + return false; +} + +// 48000 sample rate,48 samples/ms * 20ms * 2 channel = 1920 +void OpusEncoderImpl::EncodeRun() { + m_thread = std::make_unique([this]() { + const int frame_size = 48 * 20; // 960 + const int input_len = sizeof(opus_int16) * frame_size * 2; + + OpusDecoderImpl decoder(48000, channel_num); + + opus_int16 input_data[frame_size * 2] = {0}; + unsigned char input_buffer[input_len] = {0}; + unsigned char out_data[MAX_PACKET_SIZE] = {0}; + + while (isRuning) { + if (pcm_queue.size() >= input_len) { + mutex.lock(); + for (int i = 0; i < input_len; i++) { + input_buffer[i] = pcm_queue.front(); + pcm_queue.pop(); + } + + mutex.unlock(); + + auto ret = opus_encode(encoder, (opus_int16 *)input_buffer, frame_size, + out_data, MAX_PACKET_SIZE); + if (ret < 0) { + printf("opus decode failed, %d\n", ret); + break; + } + + unsigned char *opus_buffer = (unsigned char *)malloc(ret); + memcpy(opus_buffer, out_data, ret); + decoder.Decode(opus_buffer, ret); + + StreamInfo info; + info.data = opus_buffer; + info.len = ret; + info.dts = 20; + access_mutex.lock(); + info_queue.push(info); + access_mutex.unlock(); + + } else { + usleep(1000); + } + } + }); +} + +void OpusEncoderImpl::Stop() { + isRuning = false; + m_thread->join(); + + while (pcm_queue.size() > 0) { + pcm_queue.pop(); + } + + opus_encoder_destroy(encoder); +} + +OpusEncoderImpl::~OpusEncoderImpl() {} \ No newline at end of file diff --git a/tests/opus/OpusEncoderImpl.h b/tests/opus/OpusEncoderImpl.h new file mode 100644 index 0000000..61732f6 --- /dev/null +++ b/tests/opus/OpusEncoderImpl.h @@ -0,0 +1,33 @@ + +#ifndef __OPUSENCODERIMPL_H +#define __OPUSENCODERIMPL_H +#include +#include +#include +#include + +#include "base_type.h" +#include "opus/opus.h" + +class OpusEncoderImpl { + private: + OpusEncoder *encoder; + const int channel_num; + int sample_rate; + std::queue info_queue; + std::queue pcm_queue; + std::mutex mutex; + bool isRuning = true; + std::mutex access_mutex; + std::unique_ptr m_thread; + + public: + OpusEncoderImpl(int sampleRate, int channel); + void Feed(unsigned char *data, int len); + bool PopFrame(StreamInfo &info); + void EncodeRun(); + void Stop(); + ~OpusEncoderImpl(); +}; + +#endif \ No newline at end of file diff --git a/tests/opus/base_type.h b/tests/opus/base_type.h new file mode 100644 index 0000000..0059b11 --- /dev/null +++ b/tests/opus/base_type.h @@ -0,0 +1,10 @@ + +#ifndef __BASE_TYPE_H__ +#define __BASE_TYPE_H__ +typedef struct StreamInfo { + unsigned char *data; + int len; + int dts; +} StreamInfo; + +#endif \ No newline at end of file diff --git a/tests/opus/main.cpp b/tests/opus/main.cpp new file mode 100644 index 0000000..2703e6a --- /dev/null +++ b/tests/opus/main.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "OpusEncoderImpl.h" +#include "opus/opus.h" + +int main() { + OpusEncoderImpl* opusEncoder = new OpusEncoderImpl(48000, 2); + + std::ifstream inputFile("ls.pcm", std::ios::binary); + if (!inputFile) { + std::cerr << "Failed to open input file." << std::endl; + return -1; + } + + char sample[960]; + while (inputFile.read(sample, 960)) { + opusEncoder->Feed((unsigned char*)sample, 960); + } + + // // 读取编码后的opus,一般放在单独线程,这里只是为了方便 + // StreamInfo info; + // while (opusEncoder.PopFrame(info)) { + // ..... + // } + + opusEncoder->Stop(); + + return 0; +} \ No newline at end of file diff --git a/tests/opus/opus_test.cpp b/tests/opus/opus_test.cpp new file mode 100644 index 0000000..ce9dde2 --- /dev/null +++ b/tests/opus/opus_test.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +// Opus编码函数 +#include + +#include +#include + +#define SAMPLE_RATE 48000 +#define CHANNELS 2 +#define FRAME_SIZE 960 +#define APPLICATION OPUS_APPLICATION_AUDIO + +// 编码函数 +int encode(const std::vector& pcm, + std::vector& opus) { + // 创建编码器 + int error; + OpusEncoder* encoder = + opus_encoder_create(SAMPLE_RATE, CHANNELS, APPLICATION, &error); + if (error != OPUS_OK) { + std::cerr << "Failed to create encoder: " << opus_strerror(error) + << std::endl; + return error; + } + + // 设置编码器参数 + opus_encoder_ctl(encoder, OPUS_SET_BITRATE(64000)); + + // 计算最大输出大小 + int maxOpusSize = FRAME_SIZE * CHANNELS * sizeof(opus_int16); + opus.resize(maxOpusSize); + + // 编码 + int encodedSize = + opus_encode(encoder, pcm.data(), FRAME_SIZE, opus.data(), maxOpusSize); + if (encodedSize < 0) { + std::cerr << "Encoding error: " << opus_strerror(encodedSize) << std::endl; + return encodedSize; + } + + // 清理资源 + opus_encoder_destroy(encoder); + + // 调整输出向量的大小 + opus.resize(encodedSize); + + return 0; +} + +int main(int argc, char** argv) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " input.pcm output.opus" << std::endl; + return -1; + } + + // 打开输入文件 + std::ifstream inputFile(argv[1], std::ios::binary); + if (!inputFile) { + std::cerr << "Failed to open input file." << std::endl; + return -1; + } + + // 读取PCM数据 + std::vector pcmData; + opus_int16 sample; + while (inputFile.read(reinterpret_cast(&sample), sizeof(opus_int16))) { + pcmData.push_back(sample); + } + + // 编码为Opus格式 + std::vector opusData; + int result = encode(pcmData, opusData); + if (result != 0) { + std::cerr << "Encoding failed with error code " << result << std::endl; + return result; + } + + // 打开输出文件 + std::ofstream outputFile(argv[2], std::ios::binary); + if (!outputFile) { + std::cerr << "Failed to open output file." << std::endl; + return -1; + } + + // 写入Opus数据 + outputFile.write(reinterpret_cast(opusData.data()), + opusData.size()); + + // 完成 + std::cout << "Encoding complete. size:" << pcmData.size() * 2 << std::endl; + + return 0; +} diff --git a/xmake.lua b/xmake.lua index 96e0384..91f88bb 100644 --- a/xmake.lua +++ b/xmake.lua @@ -27,7 +27,7 @@ elseif is_os("macosx") then add_ldflags("-ld_classic", {force = true}) end -add_requires("asio 1.24.0", "nlohmann_json", "spdlog 1.11.0", "openfec") +add_requires("asio 1.24.0", "nlohmann_json", "spdlog 1.11.0", "openfec", "libopus 1.4") add_packages("spdlog", "openfec") includes("thirdparty") @@ -188,6 +188,11 @@ target("media") "src/media/video/encode/ffmpeg", "src/media/video/decode/ffmpeg", {public = true}) end + add_packages("opus") + add_files("src/media/audio/encode/*.cpp", + "src/media/audio/decode/*.cpp") + add_includedirs("src/media/audio/encode", + "src/media/audio/decode", {public = true}) target("qos") set_kind("static") @@ -257,4 +262,12 @@ target("projectx") -- set_kind("binary") -- add_packages("openfec") -- add_files("tests/fec/simple_server.cpp") --- add_includedirs("tests/fec") \ No newline at end of file +-- add_includedirs("tests/fec") + +target("opus_test") + set_kind("binary") + add_packages("libopus") + add_files("tests/opus/OpusEncoderImpl.cpp", + "tests/opus/OpusDecoderImpl.cpp", + "tests/opus/main.cpp") + add_includedirs("tests/opus") \ No newline at end of file