音频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下载地址(任选一个)

相关推荐
在逃大马猴32 分钟前
如何将avi格式转换为flv格式呢?
音视频
2401_856654511 小时前
2024年搬砖小能手常备的免费录屏软件Top3
人工智能·编辑器·电脑·音视频·视频
轻口味2 小时前
HarmonyOS 音视频之音频采集实战
华为·音视频·harmonyos
专讲冷知识2 小时前
AI用Alice_split_toolset切割音频的采样率
人工智能·音视频
之群害马2 小时前
批量合并视频/图像,合并前重命名避免覆盖
音视频
唯余旧忆3 小时前
【问题处理】海康视频websocket代理问题(websocket在业务系统https协议下调用海康ws协议)
websocket·https·音视频
D^ε^S4 小时前
RTSP系列四:RTSP Server/Client实战项目
网络·音视频
超爱找事4 小时前
视频发光字体特效怎么做 会声会影字体怎么淡化退出 视频剪辑制作教程
经验分享·音视频·知识图谱·字幕·会声会影·视频剪辑制作教程
小李飞刀李寻欢4 小时前
ffmpeg获得视频的音频文件
ffmpeg·音视频·video·audio
耿彬5 小时前
Python对腾讯课程视频链接自动化抓取
python·自动化·音视频