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数据,我怎么知道
关于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. 参考文章
- opus编解码的特色和优点 - 虚生 - 博客园
- libopus 实现pcm 编码到opus-CSDN博客
- Opus从入门到精通(二)_编解码器使用_android_轻口味_InfoQ写作社区
- 【音视频 _ opus】opus编解码库(opus-1.4)详细介绍以及使用------附带解码示例代码 - 技术栈
- Opus 音频编码格式 · 陈亮的个人博客
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;
}