音频demo:将PCM数据和opus格式相互编解码

1、README

a. opus库移植步骤

源码下载地址:https://opus-codec.org/

tar xzf opus-1.5.2.tar.gz 
cd opus-1.5.2/
./configure --prefix=$PWD/_install
make -j8
make install
strip --strip-unneeded _install/lib/
tree _install/
_install/
├── [4.0K]  include
│   └── [4.0K]  opus
│       ├── [ 36K]  opus_defines.h
│       ├── [ 53K]  opus.h
│       ├── [ 33K]  opus_multistream.h
│       ├── [ 28K]  opus_projection.h
│       └── [5.0K]  opus_types.h
├── [4.0K]  lib
│   ├── [727K]  libopus.a
│   ├── [1006]  libopus.la
│   ├── [  17]  libopus.so -> libopus.so.0.10.1
│   ├── [  17]  libopus.so.0 -> libopus.so.0.10.1
│   ├── [374K]  libopus.so.0.10.1
│   └── [4.0K]  pkgconfig
│       └── [ 418]  opus.pc
└── [4.0K]  share
    └── [4.0K]  aclocal
        └── [4.0K]  opus.m4

6 directories, 12 files
b. 说明
demo现状
  • main_xxx2yyy_gpt.c的文件是chatgpt生成的demo,没有实际验证效果,仅做参考

  • main_pcm2opus.c编码:程序可以正常运行,但是编码出来没有播放器可以验证,不好确定设置的码率是否生效,不过可以通过下面自己再解码成PCM即可验证是否正常;

  • main_opus2pcm.c解码:在得到编码数据时,先写4个字节标识一下这段数据的大小,方便解码得到这一帧的数据长度。有问题,定位中。另外,比较疑惑的是opus_decode函数len入参和输出buf的frame_size入参。

    • 我拿到一个opus数据,我怎么知道len入参应该是多少呢?是不是我随便往接口里塞数据就可以给我缓存没有解码完的非完全一帧的数据呢?
    • 按照头文件注释说frame_size入参是单个通道的采样数,但是解码demo里面的又不对,还需要继续深入学习。。。
关于opus格式的说明
  • 一个有损音频压缩的数字音频编码格式,由Xiph.Org基金会开发;
  • 目标是希望用单一格式包含声音和语音,取代Speex和Vorbis,且适用于网络上低延迟的即时声音传输;
  • 是一个开放格式,使用上没有任何专利或限制;
  • Opus集成了两种声音编码的技术:以语音编码为导向的SILK和低延迟的CELT;
  • 可以无缝调节高低比特率;
  • Opus具有非常低的算法延迟(默认为22.5ms),非常适合用于低延迟语音通话的编码,也可以透过降低编码比特率,达成更低的算法延迟,最低可以到5 ms;
  • Opus都比MP3、 AAC、 HE-AAC等常见格式,有更低的延迟和更好的声音压缩率;
  • 6 kb/秒到510 kb/秒的比特率;单一频道最高256 kb/秒;
  • 采样率从8 kHz(窄带)到48 kHz(全频);只支持8KHz、12KHz、16KHz、24KHz、48KHz。
  • 帧大小从2.5毫秒到60毫秒;
  • 支持单声道和立体声;支持多达255个音轨(多数据流的帧);
c. demo使用方法
编译
$ make clean && make
rm -rf pcm2opus opus2pcm pcm2opus_gpt opus2pcm_gpt out*
gcc main_pcm2opus.c -I./include -lopus -L./lib -lm -o pcm2opus
gcc main_opus2pcm.c -I./include -lopus -L./lib -lm -o opus2pcm
gcc main_pcm2opus_gpt.c -I./include -lopus -L./lib -lm -o pcm2opus_gpt
gcc main_opus2pcm_gpt.c -I./include -lopus -L./lib -lm -o opus2pcm_gpt
编码
$ ./pcm2opus
Usage: 
         ./pcm2opus <in-pcm-file> <sample-rate> <bits per sample> <channels> <out-opus-file> <target bps>
examples: 
         ./pcm2opus ./audio/test_8000_16_1.pcm 8000 16 1 out1.opus 40000
         ./pcm2opus ./audio/test_16000_16_2.pcm 16000 16 2 out2.opus 90000
解码
$ ./opus2pcm
Usage: 
         ./opus2pcm <in-opus-file> <sample-rate> <bits per sample> <channels> <per decode size> <out-pcm-file>
examples: 
         ./opus2pcm out1.opus 8000 16 1 199 out_8000_16_1.pcm # 199是编码时打印的opus数据大小
         ./opus2pcm out2.opus 16000 16 2 898 out_16000_16_2.pcm
d. 参考文章
e. demo目录架构
$ tree -h
.
├── [4.0K]  audio
│   ├── [1.2M]  test_16000_16_2.pcm
│   └── [313K]  test_8000_16_1.pcm
├── [4.0K]  docs
│   ├── [3.2M]  libopus 实现pcm 编码到opus-CSDN博客.mhtml
│   ├── [888K]  Opus从入门到精通()_编解码器使用_android_轻口味_InfoQ写作社区.mhtml
│   ├── [1006K]  opus编解码的特色和优点 - 虚生 - 博客园.pdf
│   ├── [901K]  Opus 音频编码格式 · 陈亮的个人博客.pdf
│   └── [893K]  【音视频 _ opus】opus编解码库(opus-1.4)详细介绍以及使用——附带解码示例代码 - 技术栈.mhtml
├── [4.0K]  include
│   └── [4.0K]  opus
│       ├── [ 36K]  opus_defines.h
│       ├── [ 53K]  opus.h
│       ├── [ 33K]  opus_multistream.h
│       ├── [ 28K]  opus_projection.h
│       └── [5.0K]  opus_types.h
├── [4.0K]  lib
│   └── [727K]  libopus.a
├── [3.7K]  main_opus2pcm.c
├── [1.6K]  main_opus2pcm_gpt.c
├── [4.6K]  main_pcm2opus.c
├── [1.7K]  main_pcm2opus_gpt.c
├── [ 504]  Makefile
├── [4.0K]  opensource
│   └── [7.5M]  opus-1.5.2.tar.gz
└── [1.8K]  README.md

6 directories, 20 files

2、主要代码片段

main_pcm2opus.c
#include <stdio.h>
#include <stdlib.h>

#include "opus/opus.h"

int main(int argc, char *argv[])
{
    /* 输入PCM文件相关信息 */
    char *in_pcm_file_name = NULL;
    FILE *fp_in_pcm = NULL;
    unsigned int sample_rate = 0;
    unsigned int bits_per_sample = 0;
    unsigned int channels = 0;
    unsigned char *buffer = NULL;
    /* 输出OPUS文件相关信息 */
    char *out_opus_file_name = NULL;
    FILE *fp_out_opus = NULL;
    int error = 0;
    OpusEncoder *encoder = NULL;
    unsigned int target_bps = 0;

    /* 检查参数 */
    if(argc != 7)
    {
        printf("Usage: \n"
                "\t %s <in-pcm-file> <sample-rate> <bits per sample> <channels> <out-opus-file> <target bps>\n"
                "examples: \n"
                "\t %s ./audio/test_8000_16_1.pcm 8000 16 1 out1.opus 40000\n"
                "\t %s ./audio/test_16000_16_2.pcm 16000 16 2 out2.opus 90000\n"
                , argv[0], argv[0], argv[0]);
        return -1;
    }
    in_pcm_file_name = argv[1];
    sample_rate = atoi(argv[2]);
    bits_per_sample = atoi(argv[3]);
    channels = atoi(argv[4]);
    out_opus_file_name = argv[5];
    target_bps = atoi(argv[6]);

    switch(sample_rate)
    {
        case 8000:
        case 12000:
        case 16000:
        case 24000:
        case 48000:
            break;
        default:
            fprintf(stderr, "OPUS format only support sample rate: 8000, 12000, 16000, 24000, or 48000.!\n");
            return -1;
            break;
    }
    switch(channels)
    {
        case 1:
        case 2:
            break;
        default:
            fprintf(stderr, "OPUS format only support channels: 1 or 2.!\n");
            return -1;
            break;
    }

    // step1: 创建编码器,也可以按照 opus_encoder_get_size + malloc + opus_encoder_init 方法配置
    int application = OPUS_APPLICATION_AUDIO;
    encoder = opus_encoder_create(sample_rate, channels, application, &error);
    if(encoder == NULL || error != OPUS_OK )
    {
        fprintf(stderr, "opus_encoder_create failed!\n");
        return -1;
    }

    // step2: 配置编码器
    //int signal_type = OPUS_SIGNAL_VOICE;
    int signal_type = OPUS_SIGNAL_MUSIC;
    opus_encoder_ctl(encoder, OPUS_SET_VBR(1)); // 0:CBR, 1:VBR
    opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(1));
    opus_encoder_ctl(encoder, OPUS_SET_BITRATE(target_bps));
    opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8)); // 8 0~10
    opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(signal_type));
    opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(bits_per_sample)); // 每个采样点位数
    opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
    opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0));


    /* 打开pcm/opus文件 */
    fp_in_pcm = fopen(in_pcm_file_name, "rb");
    if(fp_in_pcm == NULL)
    {
        perror("open pcm file failed!");
        return -1;
    }
    fp_out_opus = fopen(out_opus_file_name, "wb");
    if(fp_out_opus == NULL)
    {
        perror("create opus file failed!");
        return -1;
    }

    unsigned int encode_per_samples = sample_rate / 50; // 20ms, 只能是 2.5, 5, 10, 20, 40 或 60 毫秒
    unsigned int encode_per_size = encode_per_samples * bits_per_sample/8 * channels;
    unsigned char *read_buff = calloc(encode_per_size, 1);
    unsigned char *encoded_buff = calloc(encode_per_size, 1); // 最大也就跟没压缩一样大小
    printf("It will encode pcm file. (sample_rate: %d, channels: %d, encode_per_samples: %d, encode_per_size: %d)\n",
        sample_rate, channels, encode_per_samples, encode_per_size);
    while(1)
    {
        int len = 0;
        len = fread(read_buff, 1, encode_per_size, fp_in_pcm);
        printf("--- read src pcm data len: %d\n", len);
        if(len <= 0)
        {
            // file end
            printf("file end or error with read return value(%d).\n", len);
            break;
        }

        // step3: 编码
        len = opus_encode(encoder, (const opus_int16 *)read_buff, encode_per_samples, encoded_buff, encode_per_size);
        printf("+++ encoder output opus data len: %d\n", len);
        if(len > 0)
        {
            /* 写入帧大小 */
            /* LRM: 这里写入接下来编码的数据大小,这里记录方便后面解码知道要读多少,不是标准的opus文件格式 */
            fwrite(&len, 1, sizeof(len), fp_out_opus);
            /* 写入编码数据 */
            /* LRM: 这里写入编码后的opus数据 */
            fwrite(encoded_buff, 1, len, fp_out_opus);
        }
        else
        {
            perror("opus_encode failed!");
            break;
        }
    }

    // step4: 销毁编码器
    opus_encoder_destroy(encoder);

    /* 关闭pcm/opus文件 */
    fclose(fp_in_pcm);
    fclose(fp_out_opus);
    free(read_buff);
    free(encoded_buff);

    return 0;
}
main_opus2pcm.c
#include <stdio.h>
#include <stdlib.h>

#include "opus/opus.h"

int main(int argc, char *argv[])
{
    /* 输出PCM文件相关信息 */
    char *out_pcm_file_name = NULL;
    FILE *fp_out_pcm = NULL;
    unsigned int sample_rate = 0;
    unsigned int bits_per_sample = 0;
    unsigned int channels = 0;
    unsigned int decode_per_size = 0;
    unsigned char *buffer = NULL;
    /* 输入OPUS文件相关信息 */
    char *in_opus_file_name = NULL;
    FILE *fp_in_opus = NULL;
    int error = 0;
    OpusDecoder *decoder = NULL;


    /* 检查参数 */
    if(argc != 6)
    {
        printf("Usage: \n"
                "\t %s <in-opus-file> <sample-rate> <bits per sample> <channels> <out-pcm-file>\n"
                "examples: \n"
                "\t %s out1.opus 8000 16 1 out_8000_16_1.pcm\n"
                "\t %s out2.opus 16000 16 2 out_16000_16_2.pcm\n"
                , argv[0], argv[0], argv[0]);
        return -1;
    }
    in_opus_file_name = argv[1];
    sample_rate = atoi(argv[2]);
    bits_per_sample = atoi(argv[3]);
    channels = atoi(argv[4]);
    out_pcm_file_name = argv[5];

    switch(sample_rate)
    {
        case 8000:
        case 12000:
        case 16000:
        case 24000:
        case 48000:
            break;
        default:
            fprintf(stderr, "%d sample rate not support for OPUS!\n", sample_rate);
            return -1;
            break;
    }
    switch(channels)
    {
        case 1:
        case 2:
            break;
        default:
            fprintf(stderr, "OPUS format only support channels: 1 or 2.!\n");
            return -1;
            break;
    }

    // step1: 创建编码器,也可以按照 opus_decoder_get_size + malloc + opus_decoder_init 方法配置
    decoder = opus_decoder_create(sample_rate, channels, &error);
    if(decoder == NULL || error != OPUS_OK )
    {
        fprintf(stderr, "opus_encoder_create failed!\n");
        return -1;
    }

    // step2: 配置编码器
    opus_decoder_ctl(decoder, OPUS_SET_LSB_DEPTH(bits_per_sample));

    /* 打开pcm/opus文件 */
    fp_in_opus = fopen(in_opus_file_name, "rb");
    if(fp_in_opus == NULL)
    {
        perror("open opus file failed!");
        return -1;
    }
    fp_out_pcm = fopen(out_pcm_file_name, "wb");
    if(fp_out_pcm == NULL)
    {
        perror("create pcm file failed!");
        return -1;
    }

    unsigned int decode_per_samples = sample_rate / 50; // 20ms, 只能是 2.5, 5, 10, 20, 40 或 60 毫秒
    unsigned int decode_out_per_size = decode_per_samples * bits_per_sample/8 * channels;
    unsigned char *read_buff = calloc(decode_out_per_size, 1);
    unsigned char *decoded_buff = calloc(decode_out_per_size, 1); // 1s/50=20ms, 最大也就跟没压缩一样大小
    printf("It will decode opus file. (sample_rate: %d, channels: %d, decode out buf size: %d)\n",
        sample_rate, channels, decode_out_per_size);
    while(1)
    {
        int len = 0;
        int opus_bytes = 0;
        /* LRM: 这里读出接下来的opus编码数据长度,对应于我们编码时写入的数据长度 */
        len = fread(&opus_bytes, 1, sizeof(opus_bytes), fp_in_opus);
        if(len <= 0)
        {
            // file end
            printf("file end or error with read return value(%d).\n", len);
            break;
        }

        /* LRM: 这里读出编码后的opus数据 */
        len = fread(read_buff, 1, opus_bytes, fp_in_opus);
        printf("--- read src opus data len: %d\n", len);
        if(len <= 0)
        {
            // file end
            printf("file end or error with read return value(%d).\n", len);
            break;
        }

        // step3: 编码
        //len = opus_decode(decoder, (const unsigned char *)read_buff, len, (opus_int16 *)decoded_buff, decode_per_samples, 0);
        //len = opus_decode(decoder, (const unsigned char *)read_buff, len, (opus_int16 *)decoded_buff, decode_per_samples * bits_per_sample/8, 0);
        len = opus_decode(decoder, (const unsigned char *)read_buff, len, (opus_int16 *)decoded_buff, decode_per_samples, 0);
        printf("+++ decode output opus data len: %d\n", len);
        if(len > 0)
        {
            fwrite(decoded_buff, sizeof(short)*channels, len, fp_out_pcm);
        }
        else
        {
            perror("opus_decode failed!");
            break;
        }
    }

    // step4: 销毁编码器
    opus_decoder_destroy(decoder);

    /* 关闭pcm/opus文件 */
    fclose(fp_in_opus);
    fclose(fp_out_pcm);
    free(read_buff);
    free(decoded_buff);

    return 0;
}

3、demo下载地址(任选一个)

<think>好的,用户想了解如何在ESP-IDF框架中移植OPUS编解码器。首先,我需要回忆一下ESP-IDF的相关知识,以及OPUS的集成方法。记得ESP-IDF是乐鑫官方的物联网开发框架,而OPUS是一个高效的音频编解码器,常用于语音音频传输。 用户提到了两个引用,其中引用[1][2]提到了使用ESP-ADF直接使用ESP-IDF的方法。可能用户希望不依赖ESP-ADF,直接移植到原生ESP-IDF中。根据引用[2],已经有通过ESP-IDFlibopus实现的例子,所以可能需要指导用户如何添加libopus库到项目中。 首先,步骤应该是配置环境,安装必要的工具链ESP-IDF。然后,获取libopus的源代码,可能从官方仓库克隆或者作为组件添加到项目中。ESP-IDF的组件系统允许将第三方库作为组件集成,所以需要指导用户将opus放在components目录下,并编写CMakeLists.txt文件来编译它。 接下来,用户需要配置编解码参数,比如采样率、比特率、帧大小等。这部分可能需要调用opus库的初始化函数,比如opus_encoder_createopus_decoder_create。然后,编写编码解码的函数,处理PCM数据。 另外,引用[3]提到了项目中使用opus编码/解码器,并且有同步机制环形缓冲区,这可能涉及到音频流的实时处理。用户可能需要了解如何管理缓冲区,处理网络传输中的抖动,但当前问题主要是移植OPUS,所以这部分可能暂时不需要深入,但可以作为扩展内容提及。 示例代码方面,需要展示如何初始化编码器,处理PCM数据块,并将编码后的数据输出。同时,要提醒用户注意内存管理错误处理,比如检查编码器是否创建成功,释放资源等。 可能的问题点包括:组件是否正确配置,CMakeLists.txt是否正确编写,编译器选项是否兼容(比如-fstack-protector问题),以及实时处理中的延迟缓冲区管理。需要确保用户按照步骤添加组件,并处理可能的编译错误。 最后,生成相关问题,帮助用户进一步了解优化、实时流传输与其他框架的比较,比如ESP-ADF。同时,确保引用正确的来源,如引用[2][3]提到的直接使用ESP-IDF项目结构。</think>以下是将OPUS音频编解码器移植到ESP-IDF框架的详细步骤示例: ### 一、环境配置 1. **安装ESP-IDF** 确保已安装最新版ESP-IDF(v5.1+),使用命令: ```bash git clone -b v5.1.2 --recursive https://github.com/espressif/esp-idf.git cd esp-idf && ./install.sh ``` 2. **添加OPUS组件** 在项目目录下创建`components/opus`文件夹,从官方仓库克隆源码: ```bash cd your_project/components git clone https://github.com/xiph/opus.git ``` ### 二、组件配置 1. **编写CMakeLists.txt** 在`components/opus`中添加: ```cmake idf_component_register( SRCS "src/opus.c" "src/opus_decoder.c" "src/opus_encoder.c" INCLUDE_DIRS "include" REQUIRES esp_timer ) ``` 2. **配置编译器选项** 在`menuconfig`中关闭`-fstack-protector`(防止与OPUS汇编代码冲突): ```bash idf.py menuconfig → Compiler options → Disable stack protection ``` ### 三、编码实现(PCMOPUS) ```c #include "opus.h" #define SAMPLE_RATE 16000 #define CHANNELS 1 #define FRAME_SIZE 320 // 20ms帧 OpusEncoder* init_opus_encoder() { int err; OpusEncoder* enc = opus_encoder_create( SAMPLE_RATE, CHANNELS, OPUS_APPLICATION_VOIP, &err ); opus_encoder_ctl(enc, OPUS_SET_BITRATE(24000)); // 24kbps return enc; } void encode_pcm_frame(OpusEncoder* enc, const int16_t* pcm, uint8_t* output, int max_size) { int len = opus_encode(enc, pcm, FRAME_SIZE, output, max_size); if (len < 0) { ESP_LOGE("OPUS", "编码失败: %s", opus_strerror(len)); } } ``` ### 四、解码实现(OPUSPCM) ```c OpusDecoder* init_opus_decoder() { int err; OpusDecoder* dec = opus_decoder_create( SAMPLE_RATE, CHANNELS, &err ); return dec; } void decode_opus_frame(OpusDecoder* dec, const uint8_t* data, int len, int16_t* pcm_out) { int samples = opus_decode(dec, data, len, pcm_out, FRAME_SIZE, 0); if (samples < 0) { ESP_LOGE("OPUS", "解码失败: %s", opus_strerror(samples)); } } ``` ### 五、集成到ESP32-C3 1. **内存优化** 在`sdkconfig.defaults`中增加堆空间: ``` CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 CONFIG_OPUS_STACK_SIZE=6144 ``` 2. **实时音频流处理** 建议使用双缓冲区FreeRTOS任务实现实时编解码同步[^3]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

R-QWERT

你的鼓励是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值