opus 是一种音频格式,常用于语音通话、视频会议中。最近做了个pcm 到opus 的编码,踩了不少坑,特此记录一下。
目录
1、基础知识
opus 支持2.5、5、10、20、40、60ms 等帧长,对于一个48000khz 的 16bit,双通道,20 ms 的pcm 音频来说,每ms 样本数为 48000/1000 = 48,采用位深为16bit/8 = 2byte,所以需要的pcm 字节数为
pcm size = 48 样本/ms X 20ms X 2byte X 2 channel = 3840 byte
对于采样为16 bit 的2声道的PCM 数据来说,其内存布局如下图所示
LLLL LLLL LLLL LLLL RRRR RRRR RRRR RRRR
opus 编码函数是 opus_encode,其输入数组是 opus_int16 数组,2字节,要进行unsigned char 数组到 opus_int16 数组的转换后才能送入编码器。
2、使用流程
2.1 创建编码器
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create(
opus_int32 Fs,
int channels,
int application,
int *error
);
fs:采样率,8000,12000,16000,24000,48000 之一
channels:通道数
application:编码模式,有三种:
OPUS_APPLICATION_VOIP:对语音信号进行处理,适用于voip 业务场景
OPUS_APPLICATION_AUDIO:这个模式适用于音乐类型等非语音内容
OPUS_APPLICATION_RESTRICTED_LOWDELAY:低延迟模式
error :编码返回值
2.2 编码器配置
opus_encoder_ctl(OpusEncoder *st, int request, ...)
st:opus_encoder_create 创建的结构体
request:宏定义的配置参数
典型配置
opus_encoder_ctl(encoder, OPUS_SET_VBR(0));//0:CBR, 1:VBR
opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(true));
opus_encoder_ctl(encoder, OPUS_SET_BITRATE(96000));
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8));//8 0~10
opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));//每个采样16个bit,2个byte
opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0));
2.3 进行编码
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode(
OpusEncoder *st,
const opus_int16 *pcm,
int frame_size,
unsigned char *data,
opus_int32 max_data_bytes
)
st:opus编码器实例
pcm:输入的pcm 数据,双通道的话数据交叉存储,大小为 frame_size x channels x sizeof(ipus_int16)。
frme_size:
每个通道中输入音频信号的样本数,这里不是传pcm 数组大小,比如采用的是 48000 hz 编码,20ms 帧长,那么frame_size 应该是48*2 = 960,pcm 分配大小= frame_size x channels x sizeof(ipus_int16)。
data:输出缓冲区,接收编码后的数据
max_data_bytes:输出缓冲区大小
返回值:实际编码后输出数据大小
2.4 完整代码
base_type.h
#ifndef __BASE_TYPE_H__
#define __BASE_TYPE_H__
typedef struct StreamInfo
{
unsigned char *data;
int len;
int dts;
}StreamInfo;
#endif
OpusEncoderImpl.h
#ifndef __OPUSENCODERIMPL_H
#define __OPUSENCODERIMPL_H
#include "include/opus/opus.h"
#include <vector>
#include <mutex>
#include "base_type.h"
#include <queue>
#include <thread>
class OpusEncoderImpl
{
private:
OpusEncoder *encoder;
const int channel_num;
int sample_rate;
std::queue<StreamInfo> info_queue;
std::queue <unsigned char> pcm_queue;
std::mutex mutex;
bool isRuning = true;
std::mutex access_mutex;
std::unique_ptr<std::thread> m_thread;
public:
OpusEncoderImpl(int sampleRate, int channel);
void Feed(unsigned char*data, int len);
bool PopFrame(StreamInfo &info);
void EncodeRun();
void Stop();
~OpusEncoderImpl();
};
OpusEncoderImpl.cpp
#include "OpusEncoderImpl.h"
#include "OpusDecoderImpl.h"
#include <unistd.h>
#include <stdlib.h>
#define MAX_PACKET_SIZE 3*1276
/*
* sampleRate:采样率
* channel:通道数
*/
OpusEncoderImpl::OpusEncoderImpl(int sampleRate, int channel):channel_num(channel),sample_rate(sampleRate)
{
int err;
int applications[3] = {OPUS_APPLICATION_AUDIO, OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY};
encoder = opus_encoder_create(sampleRate, channel_num, applications[1], &err);
if(err != OPUS_OK || encoder == NULL) {
printf("打开opus 编码器失败\n");
}
opus_encoder_ctl(encoder, OPUS_SET_VBR(0));//0:CBR, 1:VBR
opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(true));
opus_encoder_ctl(encoder, OPUS_SET_BITRATE(96000));
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(8));//8 0~10
opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));//每个采样16个bit,2个byte
opus_encoder_ctl(encoder, OPUS_SET_DTX(0));
opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(0));
EncodeRun();
}
//每一帧pcm 是23ms
void OpusEncoderImpl::Feed(unsigned char *data, int len) {
mutex.lock();
for(auto i = 0;i < len;i++) {
pcm_queue.emplace(data[i]);
}
mutex.unlock();
}
bool OpusEncoderImpl::PopFrame(StreamInfo &info) {
if(info_queue.size() > 0) {
access_mutex.lock();
info = info_queue.front();
info_queue.pop();
access_mutex.unlock();
return true;
}
return false;
}
//48000 采样率,48个样本/ms * 20ms * 2 channel = 1920
void OpusEncoderImpl::EncodeRun() {
m_thread = std::make_unique<std::thread>([this](){
const int frame_size = 48*20;//960
const int input_len = sizeof(opus_int16) * frame_size * 2;
FILE *opus_file = fopen("/data/bin/out.customopus", "wb+");
FILE *pcm_file = fopen("/data/bin/out.pcm", "wb+");
OpusDecoderImpl decoder(48000, channel_num);
opus_int16 input_data[frame_size * 2] = {0};//frame_size*channels*sizeof(opus_int16)
unsigned char input_buffer[input_len] = {0};//每一帧的数据量
unsigned char out_data[MAX_PACKET_SIZE] = {0};
while (isRuning) {
if(pcm_queue.size() >= input_len) {
mutex.lock();
for(int i = 0;i < input_len;i++)
{
input_buffer[i] = pcm_queue.front();
pcm_queue.pop();
}
// for (size_t i = 0; i < frame_size * channel_num; i++)
// {
// input_data[i] = input_buffer[2*i + 1] << 8 | input_buffer[2*i];
// }
mutex.unlock();
memcpy(input_data, input_buffer, input_len);
// fwrite(input_buffer, 1, input_len, pcm_file);
// fflush(pcm_file);
auto ret = opus_encode(encoder, input_data, frame_size, out_data, MAX_PACKET_SIZE);
if(ret < 0) {
printf("opus编码失败, %d\n", ret);
break;
}
//写入文件
// uint32_t len = static_cast<int>(ret);
// fwrite(&len, 1, sizeof(uint32_t), opus_file);
//fwrite(out_data, 1, ret, opus_file);
//fflush(opus_file);
unsigned char* opus_buffer = (unsigned char*)malloc(ret);
memcpy(opus_buffer, out_data, ret);
//decoder.Decode(opus_buffer, ret);
StreamInfo info;
info.data = opus_buffer;
info.len = ret;
info.dts = 20;
access_mutex.lock();
info_queue.push(info);
access_mutex.unlock();
}else {
usleep(1000);
}
}
});
}
void OpusEncoderImpl::Stop() {
isRuning = false;
m_thread->join();
while (pcm_queue.size() > 0)
{
pcm_queue.pop();
}
opus_encoder_destroy(encoder);
}
OpusEncoderImpl::~OpusEncoderImpl() {
}
3、结果验证
验证方法一是将编码后的文件打包成ogg,或者将编码后的数据再解码成pcm,用audacity 查看,这里是采用的是后者。
OpusDecoderImpl.h
#ifndef __OPUSDECODERIMPL_H
#define __OPUSDECODERIMPL_H
#include <stdio.h>
#include "include/opus/opus.h"
#include <vector>
#include <mutex>
#include "base_type.h"
#include <queue>
#include <thread>
class OpusDecoderImpl
{
private:
/* data */
OpusDecoder *decoder;
int sample_rate;
int channel_num;
FILE *pcm_file;
public:
bool Decode(unsigned char* in_data, int len);
OpusDecoderImpl(int sampleRate, int channel);
~OpusDecoderImpl();
};
#endif
OpusDecoderImpl.cpp
#include "OpusDecoderImpl.h"
#define MAX_FRAME_SIZE 6*960
#define CHANNELS 2
OpusDecoderImpl::OpusDecoderImpl(int sampleRate, int channel)
{
int err;
decoder = opus_decoder_create(sampleRate, channel, &err);
opus_decoder_ctl(decoder, OPUS_SET_LSB_DEPTH(16));
sample_rate = sample_rate;
channel_num = channel;
if(err < 0 || decoder == NULL)
{
printf("创建解码器失败\n");
return;
}
pcm_file = fopen("/data/bin/decode.pcm", "wb+");
}
bool OpusDecoderImpl::Decode(unsigned char* in_data, int len)
{
unsigned char pcm_bytes[MAX_FRAME_SIZE * CHANNELS * 2];
opus_int16 out[MAX_FRAME_SIZE * CHANNELS];
auto frame_size = opus_decode(decoder, in_data, len, out, MAX_FRAME_SIZE, 0);
if (frame_size < 0)
{
printf("解码失败\n");
return false;
}
for (auto i = 0; i < channel_num * frame_size; i++)
{
pcm_bytes[2 * i] = out[i] & 0xFF;
pcm_bytes[2 * i + 1] = (out[i] >> 8) & 0xFF;
}
fwrite(pcm_bytes, sizeof(short), frame_size * channel_num, pcm_file);
fflush(pcm_file);
return true;
}
OpusDecoderImpl::~OpusDecoderImpl()
{
}
补上函数调用(ps:代码可能会有语法错误,请自行解决)
int main(int argc, char** argc)
{
OpusEncoderImpl opusEncoder = new OpusEncoderImpl(48000, 2);
for (size_t i = 0; i < 100; i++)
{
opusEncoder.Feed(pcm_data, pcm_len);//送pcm 数据编码
}
//读取编码后的opus,一般放在单独线程,这里只是为了方便
StreamInfo info;
while (opusEncoder.PopFrame(info))
{
.....
}
opusEncoder.Stop();
}
4、参考资料
音频和OPUS开源库简介_WuYuJun’s blog的博客-CSDN博客_opus库