opus 是一种音频格式,常用于语音通话、视频会议中。最近做了个pcm 到opus 的编码,踩了不少坑,特此记录一下。
目录
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 数组的转换后才能送入编码器。
- 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 :编码返回值
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));
- 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:输出缓冲区大小
返回值:实际编码后输出数据大小
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
- #include
- #include "base_type.h"
- #include
- #include
-
-
- class OpusEncoderImpl
- {
- private:
- OpusEncoder *encoder;
- const int channel_num;
- int sample_rate;
- std::queue
info_queue; - std::queue <unsigned char> pcm_queue;
- std::mutex mutex;
- bool isRuning = true;
- std::mutex access_mutex;
- std::unique_ptr
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
- #include
- #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
([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
(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() {
-
- }
验证方法一是将编码后的文件打包成ogg,或者将编码后的数据再解码成pcm,用audacity 查看,这里是采用的是后者。
OpusDecoderImpl.h
-
- #ifndef __OPUSDECODERIMPL_H
- #define __OPUSDECODERIMPL_H
- #include
- #include "include/opus/opus.h"
- #include
- #include
- #include "base_type.h"
- #include
- #include
-
- 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();
-
- }
音频和OPUS开源库简介_WuYuJun's blog的博客-CSDN博客_opus库
音视频编解码--Opus编解码系列1_Fenngtun的博客-CSDN博客
Qt 之 opus编码_老菜鸟的每一天的博客-CSDN博客_opus编码