• libopus 实现pcm 编码到opus


    opus 是一种音频格式,常用于语音通话、视频会议中。最近做了个pcm 到opus 的编码,踩了不少坑,特此记录一下。

    目录

    1、基础知识

    2、使用流程

    2.1 创建编码器

    2.2 编码器配置

    2.3 进行编码

    2.4 完整代码

    3、结果验证

    4、参考资料


    1、基础知识

    opus 支持2.5、5、10、20、40、60ms 等帧长,对于一个48000khz 的 16bit,双通道,20 ms 的pcm 音频来说,每ms 样本数为 48000/1000 = 48,采用位深为16bit/8 = 2byte,所以需要的pcm 字节数为

       pcm size = 48 样本/ms  X 20ms X 2byte X 2 channel = 3840 byte

     对于采样为16 bit 的2声道的PCM 数据来说,其内存布局如下图所示

    LLLL LLLL LLLL LLLL RRRR RRRR RRRR RRRR

     opus 编码函数是 opus_encode,其输入数组是 opus_int16 数组,2字节,要进行unsigned char 数组到 opus_int16 数组的转换后才能送入编码器

    2、使用流程

    2.1 创建编码器

    1. OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create(
    2. opus_int32 Fs,
    3. int channels,
    4. int application,
    5. int *error
    6. );

    fs:采样率,8000,12000,16000,24000,48000 之一

    channels:通道数

    application:编码模式,有三种:

     OPUS_APPLICATION_VOIP:对语音信号进行处理,适用于voip 业务场景

     OPUS_APPLICATION_AUDIO:这个模式适用于音乐类型等非语音内容

     OPUS_APPLICATION_RESTRICTED_LOWDELAY:低延迟模式

    error :编码返回值

    2.2 编码器配置

    opus_encoder_ctl(OpusEncoder *st, int request, ...)

     st:opus_encoder_create 创建的结构体

     request:宏定义的配置参数

     典型配置

    1. opus_encoder_ctl(encoder, OPUS_SET_VBR(0));//0:CBR, 1:VBR
    2. opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(true));
    3. opus_encoder_ctl(encoder, OPUS_SET_BITRATE(96000));
    4. opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8));//8 0~10
    5. opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
    6. opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));//每个采样16个bit,2个byte
    7. opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
    8. opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0));

    2.3 进行编码

    1. OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode(
    2. OpusEncoder *st,
    3. const opus_int16 *pcm,
    4. int frame_size,
    5. unsigned char *data,
    6. opus_int32 max_data_bytes
    7. )

    st:opus编码器实例

    pcm:输入的pcm 数据,双通道的话数据交叉存储,大小为 frame_size x channels x sizeof(ipus_int16)。

    frme_size:每个通道中输入音频信号的样本数,这里不是传pcm 数组大小,比如采用的是 48000 hz 编码,20ms 帧长,那么frame_size 应该是48*2 = 960,pcm 分配大小= frame_size x channels x sizeof(ipus_int16)。

    data:输出缓冲区,接收编码后的数据

    max_data_bytes:输出缓冲区大小

    返回值:实际编码后输出数据大小

    2.4 完整代码

    base_type.h

    1. #ifndef __BASE_TYPE_H__
    2. #define __BASE_TYPE_H__
    3. typedef struct StreamInfo
    4. {
    5. unsigned char *data;
    6. int len;
    7. int dts;
    8. }StreamInfo;
    9. #endif

    OpusEncoderImpl.h

    1. #ifndef __OPUSENCODERIMPL_H
    2. #define __OPUSENCODERIMPL_H
    3. #include "include/opus/opus.h"
    4. #include
    5. #include
    6. #include "base_type.h"
    7. #include
    8. #include
    9. class OpusEncoderImpl
    10. {
    11. private:
    12. OpusEncoder *encoder;
    13. const int channel_num;
    14. int sample_rate;
    15. std::queue info_queue;
    16. std::queue <unsigned char> pcm_queue;
    17. std::mutex mutex;
    18. bool isRuning = true;
    19. std::mutex access_mutex;
    20. std::unique_ptr m_thread;
    21. public:
    22. OpusEncoderImpl(int sampleRate, int channel);
    23. void Feed(unsigned char*data, int len);
    24. bool PopFrame(StreamInfo &info);
    25. void EncodeRun();
    26. void Stop();
    27. ~OpusEncoderImpl();
    28. };

    OpusEncoderImpl.cpp

    1. #include "OpusEncoderImpl.h"
    2. #include "OpusDecoderImpl.h"
    3. #include
    4. #include
    5. #define MAX_PACKET_SIZE 3*1276
    6. /*
    7. * sampleRate:采样率
    8. * channel:通道数
    9. */
    10. OpusEncoderImpl::OpusEncoderImpl(int sampleRate, int channel):channel_num(channel),sample_rate(sampleRate)
    11. {
    12. int err;
    13. int applications[3] = {OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY};
    14. encoder = opus_encoder_create(sampleRate, channel_num, applications[1], &err);
    15. if(err != OPUS_OK || encoder == NULL) {
    16. printf("打开opus 编码器失败\n");
    17. }
    18. opus_encoder_ctl(encoder, OPUS_SET_VBR(0));//0:CBR, 1:VBR
    19. opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(true));
    20. opus_encoder_ctl(encoder, OPUS_SET_BITRATE(96000));
    21. opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8));//8 0~10
    22. opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
    23. opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));//每个采样16个bit,2个byte
    24. opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
    25. opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0));
    26. EncodeRun();
    27. }
    28. //每一帧pcm 是23ms
    29. void OpusEncoderImpl::Feed(unsigned char *data, int len) {
    30. mutex.lock();
    31. for(auto i = 0;i < len;i++) {
    32. pcm_queue.emplace(data[i]);
    33. }
    34. mutex.unlock();
    35. }
    36. bool OpusEncoderImpl::PopFrame(StreamInfo &info) {
    37. if(info_queue.size() > 0) {
    38. access_mutex.lock();
    39. info = info_queue.front();
    40. info_queue.pop();
    41. access_mutex.unlock();
    42. return true;
    43. }
    44. return false;
    45. }
    46. //48000 采样率,48个样本/ms * 20ms * 2 channel = 1920
    47. void OpusEncoderImpl::EncodeRun() {
    48. m_thread = std::make_unique([this](){
    49. const int frame_size = 48*20;//960
    50. const int input_len = sizeof(opus_int16) * frame_size * 2;
    51. FILE *opus_file = fopen("/data/bin/out.customopus", "wb+");
    52. FILE *pcm_file = fopen("/data/bin/out.pcm", "wb+");
    53. OpusDecoderImpl decoder(48000, channel_num);
    54. opus_int16 input_data[frame_size * 2] = {0};//frame_size*channels*sizeof(opus_int16)
    55. unsigned char input_buffer[input_len] = {0};//每一帧的数据量
    56. unsigned char out_data[MAX_PACKET_SIZE] = {0};
    57. while (isRuning) {
    58. if(pcm_queue.size() >= input_len) {
    59. mutex.lock();
    60. for(int i = 0;i < input_len;i++)
    61. {
    62. input_buffer[i] = pcm_queue.front();
    63. pcm_queue.pop();
    64. }
    65. // for (size_t i = 0; i < frame_size * channel_num; i++)
    66. // {
    67. // input_data[i] = input_buffer[2*i + 1] << 8 | input_buffer[2*i];
    68. // }
    69. mutex.unlock();
    70. memcpy(input_data, input_buffer, input_len);
    71. // fwrite(input_buffer, 1, input_len, pcm_file);
    72. // fflush(pcm_file);
    73. auto ret = opus_encode(encoder, input_data, frame_size, out_data, MAX_PACKET_SIZE);
    74. if(ret < 0) {
    75. printf("opus编码失败, %d\n", ret);
    76. break;
    77. }
    78. //写入文件
    79. // uint32_t len = static_cast(ret);
    80. // fwrite(&len, 1, sizeof(uint32_t), opus_file);
    81. //fwrite(out_data, 1, ret, opus_file);
    82. //fflush(opus_file);
    83. unsigned char* opus_buffer = (unsigned char*)malloc(ret);
    84. memcpy(opus_buffer, out_data, ret);
    85. //decoder.Decode(opus_buffer, ret);
    86. StreamInfo info;
    87. info.data = opus_buffer;
    88. info.len = ret;
    89. info.dts = 20;
    90. access_mutex.lock();
    91. info_queue.push(info);
    92. access_mutex.unlock();
    93. }else {
    94. usleep(1000);
    95. }
    96. }
    97. });
    98. }
    99. void OpusEncoderImpl::Stop() {
    100. isRuning = false;
    101. m_thread->join();
    102. while (pcm_queue.size() > 0)
    103. {
    104. pcm_queue.pop();
    105. }
    106. opus_encoder_destroy(encoder);
    107. }
    108. OpusEncoderImpl::~OpusEncoderImpl() {
    109. }

    3、结果验证

    验证方法一是将编码后的文件打包成ogg,或者将编码后的数据再解码成pcm,用audacity 查看,这里是采用的是后者。

    OpusDecoderImpl.h

    1. #ifndef __OPUSDECODERIMPL_H
    2. #define __OPUSDECODERIMPL_H
    3. #include
    4. #include "include/opus/opus.h"
    5. #include
    6. #include
    7. #include "base_type.h"
    8. #include
    9. #include
    10. class OpusDecoderImpl
    11. {
    12. private:
    13. /* data */
    14. OpusDecoder *decoder;
    15. int sample_rate;
    16. int channel_num;
    17. FILE *pcm_file;
    18. public:
    19. bool Decode(unsigned char* in_data, int len);
    20. OpusDecoderImpl(int sampleRate, int channel);
    21. ~OpusDecoderImpl();
    22. };
    23. #endif

    OpusDecoderImpl.cpp

    1. #include "OpusDecoderImpl.h"
    2. #define MAX_FRAME_SIZE 6*960
    3. #define CHANNELS 2
    4. OpusDecoderImpl::OpusDecoderImpl(int sampleRate, int channel)
    5. {
    6. int err;
    7. decoder = opus_decoder_create(sampleRate, channel, &err);
    8. opus_decoder_ctl(decoder, OPUS_SET_LSB_DEPTH(16));
    9. sample_rate = sample_rate;
    10. channel_num = channel;
    11. if(err < 0 || decoder == NULL)
    12. {
    13. printf("创建解码器失败\n");
    14. return;
    15. }
    16. pcm_file = fopen("/data/bin/decode.pcm", "wb+");
    17. }
    18. bool OpusDecoderImpl::Decode(unsigned char* in_data, int len)
    19. {
    20. unsigned char pcm_bytes[MAX_FRAME_SIZE * CHANNELS * 2];
    21. opus_int16 out[MAX_FRAME_SIZE * CHANNELS];
    22. auto frame_size = opus_decode(decoder, in_data, len, out, MAX_FRAME_SIZE, 0);
    23. if (frame_size < 0)
    24. {
    25. printf("解码失败\n");
    26. return false;
    27. }
    28. for (auto i = 0; i < channel_num * frame_size; i++)
    29. {
    30. pcm_bytes[2 * i] = out[i] & 0xFF;
    31. pcm_bytes[2 * i + 1] = (out[i] >> 8) & 0xFF;
    32. }
    33. fwrite(pcm_bytes, sizeof(short), frame_size * channel_num, pcm_file);
    34. fflush(pcm_file);
    35. return true;
    36. }
    37. OpusDecoderImpl::~OpusDecoderImpl()
    38. {
    39. }

    补上函数调用(ps:代码可能会有语法错误,请自行解决)

    1. int main(int argc, char** argc)
    2. {
    3. OpusEncoderImpl opusEncoder = new OpusEncoderImpl(48000, 2);
    4. for (size_t i = 0; i < 100; i++)
    5. {
    6. opusEncoder.Feed(pcm_data, pcm_len);//送pcm 数据编码
    7. }
    8. //读取编码后的opus,一般放在单独线程,这里只是为了方便
    9. StreamInfo info;
    10. while (opusEncoder.PopFrame(info))
    11. {
    12. .....
    13. }
    14. opusEncoder.Stop();
    15. }

    4、参考资料

    音频和OPUS开源库简介_WuYuJun's blog的博客-CSDN博客_opus库

    音视频编解码--Opus编解码系列1_Fenngtun的博客-CSDN博客

    Qt 之 opus编码_老菜鸟的每一天的博客-CSDN博客_opus编码

      

  • 相关阅读:
    深入探讨AJAX接口进度监控:实现步骤、代码示例与技术原理
    Apache Doris 系列: 基础篇-Flink DataStream 读写Doris
    [mtk6771] Android13 修改蓝牙默认名称
    【SSM框架】Mybatis详解03(案例源码文末自取)
    入门力扣自学笔记120 C++ (题目编号1417)
    网络安全(黑客技术)—小白自学笔记
    log4j2安全漏洞修复
    90、网络硬件一文通
    索引的优化
    vue3的watch、computed写法及扩展( 对比vue2)
  • 原文地址:https://blog.csdn.net/sinat_27720649/article/details/126530085