Opus 编码是一种有损音频编码的格式,由互联网工程任务组(IETF)进来开发,标准格式为RFC 6716
。Opus 编码是由 Skype 的 SILK 编码及 Xiph.Org 的 CELT 编码融合而成,所以既适合语音又适合音乐编码,可以从低比特率窄带语音扩展到非常高质量的立体声音乐。
总结来说,Opus 是一个高保真的适合在网络中传输的开源的语音编码格式,相对于其他编码格式来讲,保真性更好,压缩比高,延迟低。
tar -zxf opus-1.2.1.tar.gz
cd opus-1.2.1
./configure --prefix=$your_install_dir
make
make install
编译完之后, 在
$your_install_dir
目录下就有存在这三个文件夹include, lib, share
,一般可以设置为自己的工程目录或者/usr/local/
。
opus 的接口声明在include/opus.h
中,下面是四个主要的函数:
// 创建编码器
OpusEncoder *opus_encoder_create(
opus_int32 Fs, // 采样率,8000, 12000, 16000, 24000, 48000
int channels, // 声道数,网络实时音频数据一般为单通道
int application, // 语音或音乐
int *error // 是否创建成功,0为成功
)
// 修改编码器参数
int opus_encoder_ctl(
OpusEncoder *st,
int request, ...
)
opus_encoder_ctl(enc, OPUS_SET_BITRATE(24000)); // 编码比特率
opus_encoder_ctl(enc, OPUS_SET_VBR(0)); // 0恒定比特率,1可变比特率
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); // 指定编码类型,AUTO, VOICE, MUSIC可选
// PCM编码成opus
// 返回编码后的字节
// frame_size 必须是恰好是一帧(2.5, 5, 10, 20, 40, 60)ms的音频数据
// frame_size = 帧时长/(1/采样率)
opus_int32 opus_encode(
OpusEncoder *st,
const opus_int16 *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes // data的最大存储大小
)
// 释放编码器
void opus_encoder_destroy(OpusEncoder *st)
// 创建解码器
OpusDecoder *opus_decoder_create(
opus_int32 Fs,
int channels,
int *error
)
// opus解码成PCM
// **返回采样点的大小,而不是解码后的数据长度**
int opus_decode(
OpusDecoder *st, // 解码器实例
const unsigned char *data, // 要解码的数据
opus_int32 len, // 解码数据长度
opus_int16 *pcm, // 解码后的数据,是一个以16位为单位的数组
int frame_size, // 每个声道给pcm数组的长度
int decode_fec // 是否用inbandfec,0为不需要,1为需要
)
// 释放解码器
void opus_decoder_destroy(OpusDecoder *st)
opus 解码后返回的不是解码数据长度,而是采样点的大小,这应该时帧大小吧
编码器默认使用动态码率,故需要在每个压缩数据帧头部记录每一个编码帧的大小,比如用 2 个字节的头部记录这个帧长。当然也可以使用静态编码码率,编码后总是返回固定大小的 opus 编码帧。
opus_encoder_ctl(enc, OPUS_SET_VBR(0)); // 0恒定比特率,1可变比特率
在定义编码器后,可是修改编码器设置,指定静态码率,6 kb /s 到 510 kb / s 的编码比特率,想要压缩比大一点就设置小一点,但是相应失真变大。
opus_encoder_ctl(enc, OPUS_SET_BITRATE(24000)); // 编码比特率
由于 opus 集成了语音和音乐两种优秀的编码算法,所以可以对指定的音频类型进行单独优化。
opus_encoder_ctl(enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); // 指定编码类型,AUTO, VOICE, MUSIC可选
#include "opus.h"
#include "opus_types.h"
#include "opus_multistream.h"
#define SAMPLE_RATE 16000
#define CHANNEL_NUM 1
#define BIT_RATE 16000
#define BIT_PER_SAMPLE 16
#define WB_FRAME_SIZE 320
#define DATA_SIZE 1024 * 1024 * 4
// 编码
int encode(char *in, int len, unsigned char *opus, int *opus_len)
{
int err = 0;
opus_int32 skip = 0;
// 创建编码器
OpusEncoder *enc = opus_encoder_create(SAMPLE_RATE, CHANNEL_NUM,
OPUS_APPLICATION_VOIP, &err);
if (err != OPUS_OK)
{
fprintf(stderr, "cannnot create opus encoder: %s\n",
opus_strerror(err));
enc = NULL;
return -1;
}
// 修改编码器参数
opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
opus_encoder_ctl(enc, OPUS_SET_BITRATE(BIT_RATE));
opus_encoder_ctl(enc, OPUS_SET_VBR(1)); // 0:CBR, 1:VBR
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(10)); //range:0~10
opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(0));
opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(OPUS_AUTO));
opus_encoder_ctl(enc, OPUS_SET_DTX(0)); // 是否开启DTX
opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(0));
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip));
opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16));
short frame_size = WB_FRAME_SIZE;
int frame_bytes = (frame_size << 1);
opus_int16 *frame = (opus_int16 *)in;
unsigned char *cbits = opus;
while (len > frame_bytes)
{
// 编码
int nbytes = opus_encode(enc, frame, frame_size, cbits + sizeof(char),
640 - sizeof(short));
if (nbytes > frame_size * 2 || nbytes < 0)
{
return -1;
}
cbits[0] = nbytes;
frame += WB_FRAME_SIZE;
cbits += nbytes + sizeof(char);
len -= frame_bytes;
*opus_len += nbytes + sizeof(char);
}
// 释放编码器
opus_encoder_destroy(enc);
return 0;
}
// 解码
int decode(unsigned char *in, int len, short *out, int *out_len)
{
int err = 0;
opus_int32 skip = 0;
*out_len = 0;
OpusDecoder *dec = opus_decoder_create(SAMPLE_RATE, 1, &err);
if (err != OPUS_OK)
{
fprintf(stderr, "cannnot decode opus: %s\n", opus_strerror(err));
dec = NULL;
return -1;
}
short frame_size = WB_FRAME_SIZE;
opus_int16 *frame = (opus_int16 *)in;
while (len > 0)
{
int nbytes = in[0];
if (nbytes <= 0)
{
return -1;
}
int decode_len = opus_decode(dec, in + sizeof(char), nbytes, out,
frame_size, 0);
if (decode_len != frame_size)
{
return -1;
}
in += sizeof(char) + nbytes;
out += frame_size;
len -= nbytes - sizeof(char);
*out_len += frame_size;
}
opus_decoder_destroy(dec);
return 0;
}
// 编码wav文件为opus文件
int encode_wav_file(char *in_file_path, char *out_file_path)
{
FILE *fin = fopen(in_file_path, "rb");
if (fin == NULL || fin == 0)
{
return -1;
}
char *in = (char *)malloc(DATA_SIZE);
memset(in, 0, DATA_SIZE);
int len = fread(in, 1, DATA_SIZE, fin);
if (len == 0)
{
return -1;
}
FILE *fout = fopen(out_file_path, "wb");
if (fout == NULL || fout == 0)
{
return -1;
}
unsigned char *out = (unsigned char *)malloc(DATA_SIZE);
memset(out, 0, DATA_SIZE);
int out_len = 0;
encode(in, len, out, &out_len);
if (len < 0)
{
return -1;
}
fwrite(out, 1, out_len * sizeof(unsigned char), fout);
free(in);
free(out);
fclose(fin);
fclose(fout);
return len;
}
int make_wav_header(FILE *out, int len)
{
int size = 0;
int *sz = &size;
int number;
int *nm = &number;
// RIFF 4 bytes
fseek(out, 0, SEEK_SET);
fputs("RIFF", out);
// len 4 bytes
len = (len + 44 - 8);
fwrite(&len, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
// WAVE 4 bytes + "fmt " 4 bytes
fputs("WAVEfmt ", out);
// size1 4 bytes
number = 16;
fwrite(nm, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
// format tag 2 bytes
number = 1;
fwrite(nm, 2, 1, out);
// channel 2 bytes
number = CHANNEL_NUM;
fwrite(nm, 2, 1, out);
// sample rate 4 bytes
number = SAMPLE_RATE;
fwrite(nm, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
//byte per seconds 4 bytes
number = 22664;
fwrite(nm, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
// block align 2 bytes
number = CHANNEL_NUM * BIT_PER_SAMPLE / 8;
fwrite(nm, 2, 1, out);
// bitPerSample 2 bytes
number = 16;
fwrite(nm, 2, 1, out);
// "data" 4 bytes
fputs("data", out);
// size2 4 bytes
size = (size - 36);
fwrite(sz, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
return 0;
}
// 解码opus文件为wav文件
int decode_opus_file(char *in_file_path, char *out_file_path)
{
printf("%s\n", in_file_path);
FILE *fin = fopen(in_file_path, "rb");
if (fin == NULL || fin == 0)
{
return -1;
}
unsigned char *in = (unsigned char *)malloc(DATA_SIZE);
memset(in, 0, DATA_SIZE);
int len = fread(in, 1, DATA_SIZE, fin);
FILE *fout = fopen(out_file_path, "wb");
if (fout == NULL || fout == 0)
{
return -1;
}
short *out = (short *)malloc(DATA_SIZE);
memset(out, 0, DATA_SIZE);
int out_len = 0;
out += 44;
decode(in, len, (short *)out, &out_len);
if (len < 0)
{
return -1;
}
fwrite(out, 1, out_len * sizeof(short), fout);
int err = make_wav_header(fout, out_len);
free(in);
free(out);
fclose(fin);
fclose(fout);
return out_len;
}