音频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 小时前
微信小程序xr-frame透明视频实现
微信小程序·ffmpeg·音视频·xr
红米饭配南瓜汤3 小时前
WebRTC服务质量(09)- Pacer机制(01) 流程概述
网络·音视频·webrtc
是十一月末6 小时前
Python进阶之opencv图片和视频基本读取关闭
python·opencv·音视频·cv2
gomogomono9 小时前
HDR视频技术之十一:HEVCH.265 的 HDR 编码方案
音视频·h.265·hdr·yuv
Eric.Lee202110 小时前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
却道天凉_好个秋10 小时前
音视频学习(二十四):hls协议
音视频·hls
darkdragonking1 天前
FLV视频封装格式详解
音视频
元争栈道1 天前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
元争栈道1 天前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MediaTea1 天前
Pr:音频仪表
音视频