opus 是一种音频格式,常用于语音通话、视频会议中。最近做了个pcm 到opus 的编码,踩了不少坑,特此记录一下。

目录

1、基础知识

2、使用流程

2.1 创建编码器

2.2 编码器配置

2.3 进行编码

2.4 完整代码

3、结果验证

4、参考资料


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:VBRopus_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~10opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));//每个采样16个bit,2个byteopus_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:VBRopus_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~10opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(16));//每个采样16个bit,2个byteopus_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;//960const 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 2OpusDecoderImpl::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库

音视频编解码--Opus编解码系列1_Fenngtun的博客-CSDN博客

Qt 之 opus编码_老菜鸟的每一天的博客-CSDN博客_opus编码

libopus 实现pcm 编码到opus相关推荐

  1. ffmpeg 命令行 pcm 编码 opus

    将16k,16bit,单声道的pcm裸流编码成opus裸流(不带ogg封装格式) ffmpeg -ar 16000 -ac 1 -f s16le -i hello.raw -acodec opus - ...

  2. 音频编码之opus(一)

    最近项目中用到了语音编码opus,在网上搜了一下,资料非常少,而且没有一个完整的教程,现在简单记录下来opus的使用方法. 首先介绍一下opus Opus Opus编码器 是一个有损声音编码的格式,由 ...

  3. MediaCodec 完成PCM编码成AAC

    安卓MediaCodec支持的音视频格式 视频格式 public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8& ...

  4. 基于 MATLAB 的 PCM 编码解码实现

    基于 MATLAB 的 PCM 编码解码实现_vlaser的小屋-CSDN博客_pcm编码matlab

  5. a律13折线pcm编码例题_a律13折线pcm编码例题

    信息举报 时间:2020-12-20 本页为您甄选多篇描写a律13折线pcm编码例题,a律13折线pcm编码例题精选,a律13折线pcm编码例题大全,有议论,叙事 ,想象等形式.文章字数有400字.6 ...

  6. 编码 —— PCM 编码

    PCM:Pulse Code Modulation,脉冲编码调制: 1. 码率的计算 PCM约定俗成了无损编码,因为PCM代表了数字音频中最佳的保真水准,并不意味着PCM就能够确保信号绝对保真,PCM ...

  7. 对字符串进行信道编码C语言,【单选题】PCM编码主要用于实现模拟信号数字化,它属于( ) A. 信道编码 B. 纠错编码 C. 信源编码 D. 压缩编码...

    [单选题]PCM编码主要用于实现模拟信号数字化,它属于( ) A. 信道编码 B. 纠错编码 C. 信源编码 D. 压缩编码 更多相关问题 某工程采用DBB模式建设,则项目总进度目标的控制是()的任务 ...

  8. Android音视频开发,详说PCM音频重采样、PCM编码

    直播伴音,两种数据能否合在一起?不能叠加在一起 会有噪音 合并以后 再去编码推流 直播的例子 客户端播放器,可以开启多个播放器 对于我们重采样 很多时候就是为了统一格式,就是为了要合并这个流,去推送, ...

  9. matlab pcm encode,[MATLAB基础] PCM编码及解码

    回复: PCM编码及解码 在哪里搜啊.我没搜到啊.我有个关于PCM的程序.但是有错误啊.我不知道错在哪里.你帮我看看.function [out]=pcm_encode(x) n=length(x) ...

  10. python 将pcm编码文件转化为wav音频文件

    将PCM编码的数据转化为wav文件 数据类型 数据转化 原始代码以及文件下载链接 数据类型 首先是测试工具通过声卡采集的测试文本,内容如下 音频数据如下,数据呈现是时域的数值变化 数据转化 pytho ...

最新文章

  1. HDU1576 A/B (解法二)【试探法】
  2. UVa10825 Anagram and Multiplication(dfs)
  3. 程序员面试题精选100题(45)-Singleton(C/C++/C#)
  4. 使用 Maven 执行 java main class(java应用程序)
  5. ceph与hdfs的比较_分布式存储中HDFS与Ceph两者的区别是什么,各有什么优势?
  6. java 怎么快速找到实现类_JAVA懒开发:FreeMarker快速实现类的增删改查接口
  7. Golang 正在成为互联网大厂的主流编程语言!
  8. 关于spark-shell和scala关系的一些个人想法
  9. 搜索算法(一)--DFS/BFS求解拯救同伴问题(JAVA)
  10. tcp udp区别优缺点_TCP和UDP的区别
  11. GoLand tool tips
  12. Web页面iOS真机调试-win10
  13. 联想启天M5710不开机,开机后卡logo
  14. 强制删除文件处理程序
  15. SaaS前世今生:老树开新花
  16. 从零实现一个3D目标检测算法(1):3D目标检测概述
  17. 爬虫------12306
  18. 数学专业英语--极限
  19. testcenter自动化
  20. 王二是如何看到李四的《艳娘传奇》的,快来了解一下ROS2的话题机制吧!

热门文章

  1. html:optionscollection 默认值,关于html:options collection= /的使用
  2. android在体检报告叫什么,体检报告检测分析
  3. html5video拼接屏一部分黑屏,拼接屏常见问题与解决方法
  4. 计算机策略 提高网速,win10增加网速的方法_win10如何提高电脑网速
  5. 抖音下载助手GUI版 主页视频批量下载
  6. 浅析内网即时通讯工具的安全性如何
  7. c语言的二维数组的指针访问,用指针访问二维数组
  8. 【光学设计】- 第一节
  9. Java实现极光推送
  10. 三维空间坐标系变换——旋转矩阵