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

1、README

a. opus库移植步骤

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

bash 复制代码
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/
bash 复制代码
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使用方法
编译
bash 复制代码
$ 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
编码
bash 复制代码
$ ./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
解码
bash 复制代码
$ ./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目录架构
bash 复制代码
$ 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
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
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下载地址(任选一个)

相关推荐
简鹿办公2 小时前
如何提取某站 MV 视频中的音乐为 MP3 音频
音视频·简鹿视频格式转换器·视频提取mp3音频
yufengxinpian2 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
runing_an_min4 小时前
ffmpeg视频滤镜:替换部分帧-freezeframes
ffmpeg·音视频·freezeframes
runing_an_min6 小时前
ffmpeg视频滤镜:提取缩略图-framestep
ffmpeg·音视频·framestep
小曲曲7 小时前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
安静读书9 小时前
Python解析视频FPS(帧率)、分辨率信息
python·opencv·音视频
佑华硬盘拷贝机9 小时前
音频档案批量拷贝:专业SD拷贝机解决方案
音视频
EasyNVR9 小时前
NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案
安全·音视频·监控·视频监控
xcLeigh17 小时前
HTML5超酷响应式视频背景动画特效(六种风格,附源码)
前端·音视频·html5
韩曙亮18 小时前
【FFmpeg】FFmpeg 内存结构 ③ ( AVPacket 函数简介 | av_packet_ref 函数 | av_packet_clone 函数 )
ffmpeg·音视频·avpacket·av_packet_clone·av_packet_ref·ffmpeg内存结构