当前位置: 首页 > 工具软件 > Opus > 使用案例 >

Opus

堵琨
2023-12-01

Opus

Opus 简介

Opus 编码是一种有损音频编码的格式,由互联网工程任务组(IETF)进来开发,标准格式为RFC 6716。Opus 编码是由 Skype 的 SILK 编码及 Xiph.Org 的 CELT 编码融合而成,所以既适合语音又适合音乐编码,可以从低比特率窄带语音扩展到非常高质量的立体声音乐。

  • SILK 编码:由 skype 公司开源的一种语音编码,特别适合人声,适合于 Voip 语音通信。
  • CELT 编码:由 Xiph.Org 开源的编码格式,和 mp3, aac 类似,适合于传输音乐。

总结来说,Opus 是一个高保真的适合在网络中传输的开源的语音编码格式,相对于其他编码格式来讲,保真性更好,压缩比高,延迟低。

技术特点

  • 6 kb /s 到 510 kb / s 的编码码率,想要压缩比大一点就设置小一点,但是相应失真变大
  • 采样率从 8 kHz(窄带)到 48 kHz(全频),(8k, 12k, 16k, 24k, 48k)
  • 音频帧时长从 2.5 毫秒到 60 毫秒,(2.5, 5, 10, 20, 40, 60ms)
  • 支持恒定比特率(CBR)和可变比特率(VBR)
  • 支持语音和音乐
  • 支持单声道和立体声
  • 支持多达 255 个频道(多数据流的帧)
  • 可动态调节比特率,音频带宽和帧大小
  • 良好的鲁棒性丢失率和数据包丢失隐藏(PLC)
  • 浮点和定点实现

下载安装

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 主要接口

接口声明

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;
}
 类似资料:

相关阅读

相关文章

相关问答