6 Opus 编码
Opus 支持窄带(4 kHz)、中等带宽(6 kHz)、宽带(8 kHz)、超宽带(12 kHz)、全带宽(24 kHz)等多种音频带宽, 支持 2.5 ms、5 ms、10 ms、20 ms、40 ms、60 ms、80 ms、100 ms、120 ms 等 9 种帧长。 Opus 兼具较好的音质和较高的压缩率,计算复杂度也较高。音频处理库裁剪了 Opus 编码器,使其能运行于嵌入式系统。
音频处理库附带了一个 Windows 测试程序 opus_demo
,这个程序包含了完整版的解码器和裁剪过的编码器。
6.1 使用方法
初始化
使用
opus_encoder_init
初始化编码器对象:int opus_encoder_init( *st, // 编码器对象 OpusEncoder , // 采样率 opus_int32 Fsint channels, // 声道数 int application // 应用类型 );
通过
opus_encoder_get_size()
获得编码器对象的大小。采样率只能是 8000, 12000,16000,24000 或者 48000。声道数只能是 1 或者 2。应用类型及适用场景如下。OPUS_APPLICATION_VOIP
:适用于大多数 VoIP、视频会议等注重声音质量和可懂性的场景;OPUS_APPLICATION_AUDIO
:适用于广播或 Hi-Fi 等要求解码输出尽量贴近原始输入的场景;OPUS_APPLICATION_RESTRICTED_LOWDELAY
:仅用于需要最低延迟的场景。
下面的代码演示了如何从堆上分配用来存放编码器对象内存,并初始化编码器对象:
int size = opus_encoder_get_size(1); *enc = malloc(size); OpusEncoder if (NULL == enc) { ... // error handling } int error = opus_encoder_init(enc, Fs, , application); channelsif (error) { ... // error handling }
设置参数
使用
opus_encoder_ctl()
设置编码参数。opus_defines.h
里列出了所有可设置的参数。 例如,将比特率设为 80 kbps:(enc, OPUS_SET_BITRATE(80000)); opus_encoder_ctl
设置临时内存
void opus_set_scratch_mem( const void *buf, // 起始位置 int size); // 临时内存的大小(单位:字节)
在程序运行过程中,如果发现临时内存空间不足,会调用
opus_on_run_of_out_scratch_mem
。 音频库里包含了该函数的弱定义,开发者可以重新定义这个函数以自定义处理方法。这个函数的弱定义大致为:void __attribute((weak)) opus_on_run_of_out_scratch_mem( const char *fn, int line_no) { (fn, line_no); platform_raise_assertion}
请参考“参数选择与评估”了解如何确定临时内存的大小。
编码
调用
opus_encode
编码一个音频帧。( opus_int32 opus_encode*st, // 编码器对象 OpusEncoder const opus_int16 *pcm, // PCM 输入 int frame_size, // 这一帧的每个声道所包含的采样数 unsigned char *data, // 编码输出(载荷) // 编码输出的最大长度 opus_int32 max_data_bytes );
当编码两个声道时,左右声道在
pcm
里交织排列。frame_size
参数结合采样率可推算出音频帧的时长,这个音频帧的时长必须是合法,否则函数将返回一个错误码。 各种采样率所允许的frame_size
如表 6.1 所示。表 6.1: Opus 采样率与帧长 采样率 (Hz) 2.5 ms 5 ms 10 ms 20 ms 40 ms 60 ms 80 ms 100 ms 120 ms 8 k 20 40 80 160 320 480 640 800 960 12 k 30 60 120 240 480 720 960 1200 1440 16 k 40 80 160 320 640 960 1280 1600 1920 24 k 60 120 240 480 960 1440 1920 2400 2880 48 k 120 240 480 960 1920 2880 3840 4800 5760 max_data_bytes
是这一帧所允许的最大编码长度,建议预留足够大的空间,不建议用此参数进行比特率调整或控制。如果编码成功,这个函数将返回编码输出(载荷)的实际长度,否则返回错误码(负值)。
音频帧打包
opus_encode
所输出的data
仅为载荷部分,还需要附加帧长信息才能组成可解码的音频流。opus_demo
所使用的帧头结构为:- 帧长:4 字节,大端模式
- FINAL_RANGE:4 字节,大端模式
test_opus_data
函数演示了如何将编码结果保存为opus_demo
所支持的帧格式。将save_bytes()
收到的字节流保存到文件,就可以用opus_demo
解码,回听效果。void test_opus_data(OpusEncoder *enc, const int16_t *in, const int total_samples, const int sample_rate, const int samples_per_frame, uint8_t *output, const int max_output_bytes) { unsigned char int_field[4]; uint32_t enc_final_range; int i; for (i = 0; i < total_samples - samples_per_frame; += samples_per_frame) i { int r = opus_encode(enc, in + i, samples_per_frame, , max_output_bytes); outputif (r < 0) platform_raise_assertion("opus_encode", r); (int_field, 0, (uint32_t)r); big_endian_store_32(int_field, sizeof(int_field)); save_bytes (enc, OPUS_GET_FINAL_RANGE(&enc_final_range)); opus_encoder_ctl(int_field, 0, enc_final_range); big_endian_store_32(int_field, sizeof(int_field)); save_bytes (output, r); save_bytes} }
6.2 参数选择与评估
6.2.1 临时内存评估
不同的参数将显著影响所需要的临时内存的大小。运行 opus_demo
,可以得出需要的临时内存的大小。
这里使用 Audacity7 辅助转换和播放 PCM 数据。
准备测试数据
准备一个音频文件(比如一首歌曲或一段录音),使用 Audacity 按 Opus 支持的某一采样率(例如 16 kHz)导出8为单声道无格式的 16-bit PCM 文件(例如保存为
data_16k.raw
)。编码测试
运行
opus_demo
,编码测试数据。opus_demo -e audio 16000 1 100000 data_16k.raw result.enc
这里以 100 kpbs 的比特率转换为
result.enc
。程序会打印出所需要的临时空间的大小(单位:字节):stack_max_usage = 12345
解码测试
如有必要,可再运行
opus_demo
解码result.enc
:opus_demo -d 16000 1 result.enc result.dec
在 Audacity 里导入9无格式的 PCM 文件
result.dec
,采样率 16 kHz,回听编解码效果。
重复上述步骤,确定应用中所要使用的采样率、比特率等关键参数,根据工具报告的 stack_max_usage
确定临时空间大小。
6.2.2 性能评估
test_opus_performance
函数演示了如何评估编码所消耗的时间。
void test_opus_performance(OpusEncoder *enc,
const int16_t *in, const int total_samples,
const int sample_rate, const int samples_per_frame,
uint8_t *output, const int max_output_bytes)
{
int i = 0;
int frame_cnt = 0;
uint32_t total_time = 0;
for (i = 0; i < total_samples - samples_per_frame;
+= samples_per_frame, frame_cnt++)
i {
int64_t t = platform_get_us_time();
int r = opus_encode(enc, in + i, samples_per_frame,
, max_output_bytes);
outputuint32_t tt = (uint32_t)(platform_get_us_time() - t);
("%d: len = %d, %u\n", frame_cnt, r, tt);
platform_printf+= tt;
total_time }
("average time per frame = %d us\n",
platform_printf/ frame_cnt);
total_time ("scratch max used = %d bytes\n",
platform_printf());
opus_scratch_get_max_used_size}