Qt 之 opus编码
背景
项目需要实现语音翻译功能,对接的图灵的websocket api, 第一步是实现了pcm数据的上传,第二步实现pcm to opus编码,来压缩数据量。所以仅仅使用到了opus的编码功能,通过opus的设置,实现了1/8的压缩效果。
opus简介
opus是一个有损声音编码的格式,由IETF开发,没有任何专利或限制,适用于网络上的实时声音传输,标准格式为RFC 6716,其技术来源于Skype的SILK及Xiph.Org的CELT编码.
opus文件是一个用开放的Opus编解码器编码的数字音频文件。Opus文件使用Ogg容器格式。目前一些知名的媒体播放器如VLC支持直接播放.opus文件,而其他的播放器则需要先安装一个外部的Opus编解码器(“libopus”)。
Opus开发团队提供了一套参考工具,用于获取.opus文件的信息(“opusinfo”),并将它们转换为/从WAV(“opusenc”、“opusdec”)。在Microsoft Windows上,可以使用安装了免费编码器包的foobar2000创建Opus编码的音频。
主要特性如下:
- 6 kb /秒到510 kb / s的比特率
- 采样率从8 kHz(窄带)到48 kHz(全频)
- 帧大小从2.5毫秒到60毫秒
- 支持恒定比特率(CBR)和可变比特率(VBR)
- 从窄带到全频段的音频带宽
- 支持语音和音乐
- 支持单声道和立体声
- 支持多达255个频道(多数据流的帧)
- 可动态调节比特率,音频带宽和帧大小
- 良好的鲁棒性丢失率和数据包丢失隐藏(PLC)
- 浮点和定点实现
opus使用约束
采样率约束:
输入信号的采样率(Hz),必须是8000、12000、16000、24000、或48000。
OpusEncoder* opus_encoder_create(opus_int32 Fs, int channels, int application, int *error);
函数参数中的Fs就是采样率。帧长约束:
opus为了对一个帧进行编码,必须正确地用音频数据的帧(2.5, 5, 10, 20, 40 or 60 ms)来调用opus_encode()或opus_encode_float()函数。
比如,在48kHz的采样率下,opus_encode()参数中的合法的frame_size(单通道的帧大小)值只有:120, 240, 480, 960, 1920, 2880。即:
frame_size = 采样率 * 帧时间。因为需要满足帧时间长度为10,20,40,60ms这些才能编码opus,因而需要对输入数据进行缓冲裁剪
兼容opus的容器格式:
有ogg,ts,mkv。但ts无法播放,mkv只能foobar播放,ogg能用foobar,vlc播放。因而不再考虑opus合成到ts
opus环境搭建
linux环境和arm交叉编译
首先需要在opus官网上下载opus相关的源码资料
http://www.opus-codec.org/
在downloads里面可以看到全部的源码下载
这里我们需要下载
opus-tools-0.2.1.tar.gz和opus-1.3.1.tar.gz
下载后可以在ubuntu里解压
然后
./configure
(如果是其余平台如Mips或Arm,需要添加 --host=(交叉编译链),在ARM和mips平台推荐使用–enable-fixed-point命令关闭浮点运算)
#!/bin/sh
export export PATH=/home/flourier/work/gcctool/gcc-linaro-4.9-2015.02-3-x86_64_arm-linux-gnueabihf/bin:$PATH
export CC=arm-linux-gnueabihf-gcc
export CXX=arm-linux-gnueabihf-g++
指定你的host,你的交叉编译工具,还有安装目录等
./configure --host=arm-linux-gnueabihf --enable-fixed-point --disable-float-api CFLAGS="-O2 -mfpu=neon -mfloat-abi=hard" HAVE_ARM_NEON_INTR=1 --prefix /home/zyb/opus-arm/install
然后
make && make install
之后,会出现一堆供测试用的可执行文件, arm下:
linux下:
编码功能模块
由于项目需要实时编码,并且qt提供了非常方便的QIODevice,所以数据大概流程是:
- 从IODevice,拿到音频设备pcm数据
- 通过降噪处理后,push到一个缓存buf中
- opus编码线程不断的从缓存buf中,将数据去除,进行编码
- 将编码后的数据通过websocket发送的云端请求
opus实现
创建opus编码器
opus_int16 pcm_bytes[FRAME_SIZE * MAX_CHANNELS];
unsigned char cbits[MAX_PACKET_SIZE];
OpusEncoder *g_encoder = nullptr;bool creatOpusEncoder(uint32_t sampleRate,uint16_t channels,int err)
{g_encoder = opus_encoder_create(sampleRate, channels, OPUS_APPLICATION_AUDIO, &err);qDebug() << "opus_encoder_create ::::" << QString::fromLocal8Bit(opus_strerror(err));if (!g_encoder || err < 0) {qDebug() << "failed to create an encoder" << QString::fromLocal8Bit(opus_strerror(err));fprintf(stderr, "failed to create an encoder: %s\n", opus_strerror(err));if (!g_encoder) {opus_encoder_destroy(g_encoder);}return false;}opus_encoder_ctl(g_encoder, OPUS_SET_VBR(0));//0:CBR, 1:VBRopus_encoder_ctl(g_encoder, OPUS_SET_VBR_CONSTRAINT(true));opus_encoder_ctl(g_encoder, OPUS_SET_BITRATE(32000));opus_encoder_ctl(g_encoder, OPUS_SET_COMPLEXITY(8));//8 0~10opus_encoder_ctl(g_encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));opus_encoder_ctl(g_encoder, OPUS_SET_LSB_DEPTH(16));opus_encoder_ctl(g_encoder, OPUS_SET_DTX(0));opus_encoder_ctl(g_encoder, OPUS_SET_INBAND_FEC(0));//opus_encoder_ctl(g_encoder, OPUS_SET_PACKET_LOSS_PERC(0));return true;
}
其中的参数,需要根据实际的硬件才设置
比如本项目降噪后的数据是:
output_cfg.audio_channels = 1; //输出音频通道数,1通道数据
output_cfg.sample_rate = 16000; //输出音频采样率,16k
output_cfg.audio_format = kHrscAudioFormatPcm16Bit; //输出音频位数,16bit
帧长为20ms
所以比特率为 16000*2 = 32000
采样率为16000,所以一毫秒为16 20ms为320个采样,每个采样是16bit即 2个byte,所以每帧大小为640
编码
可以
传入一段长度的pcm数据,可以是frame_size*N,即数据帧的整数倍,可以根据次参数,来调节进行最大的优化
QByteArray pcmData2opus(void *audio_data,unsigned int aduio_data_size )
{QByteArray opusdata;const uint16_t *data = (uint16_t *) (audio_data);size_t size = aduio_data_size/2;size_t index = 0;size_t step = static_cast<size_t>(FRAME_SIZE * 1);size_t frameCount = 0;size_t readCount = 0;while (index < size) {memset(&pcm_bytes, 0, sizeof(pcm_bytes));if ((index + step) <= size) {memcpy(pcm_bytes, data + index, step * sizeof(uint16_t));index += step;} else {readCount = size - index;memcpy(pcm_bytes, data + index, (readCount) * sizeof(uint16_t));index += readCount;}int nbBytes = opus_encode(g_encoder, pcm_bytes, 1 * FRAME_SIZE, cbits, MAX_PACKET_SIZE);if (nbBytes < 0) {qDebug() << "encode failed:" << opus_strerror(nbBytes);break;}++frameCount;qDebug()<< "total size= "<<size << "framecount =" << frameCount << index << nbBytes;opusdata.append((char *) cbits, static_cast<size_t>(nbBytes));}return opusdata;
}
其中需要注意的是每帧大小为640,而opus_encode的函数原型为:
opus_int32 opus_encode (OpusEncoder * st,const opus_int16 * pcm,int frame_size,unsigned char * data,opus_int32 max_data_bytes
)
其中pcm数据类型为opus_int16是short类型,所以frame_size应该是 640/2 =320
也可以直接传入一帧数据,进行编码,比如下面的编码线程实现:
void OpusProcess::run()
{m_State = STATE_STARTED;m_dataBuf.clear();int err=0;bool b = creatOpusEncoder(ONLINE_ASR_AUDIO_SAMPLE_RATE,ONLINE_CHANNEL,err);while (true) {if (m_State == STATE_STOPPED)break;if(m_dataBuf.length() >= FRAME_SIZE){m_Mutex.lock();QByteArray tmpdata = m_dataBuf.left(FRAME_SIZE);quint16 nbBytes =(quint16)opus_encode(g_encoder, (opus_int16 *)tmpdata.data(), FRAME_SIZE/2, cbits, MAX_PACKET_SIZE);emit speechDataEncoded(QByteArray des((char *) cbits, nbBytes));m_dataBuf = m_dataBuf.mid(FRAME_SIZE);m_Mutex.unlock();}else {//qWarning() << "ring buffer data is not enough, need wait";usleep(100 * 1000);}}destroyOpusEncoder();m_State = STATE_STOPPED;qDebug() << "OpusProcess thread end";
}
线程结束后释放编码器:
bool destroyOpusEncoder()
{if(g_encoder){opus_encoder_destroy(g_encoder);qDebug() << "*****opus_encoder_destroy********";}
}
注意事项:
1.对于连续的一段声音,一定只能用一个解码器(不能创建之后释放再去创建解码器)
2 发送的云端的opus编码数据,一般是要加头信息的,用来告诉这段数据的长度,音频数据的每帧都要加头的。
验证
为了验证自己编码后的数据是否正确,可以用编码后的数据,再进行解码,然后把解码数据存储都一个文件,来进行播放,如果播放正常,基本数据都正常
所以,附上解码代码
OpusDecoder *g_decoder = nullptr;```cpp
bool creatOpusDecoder(uint32_t sampleRate,uint16_t channels,int err)
{g_decoder = opus_decoder_create(sampleRate, channels, &err);qDebug() << "opus_decoder_create ::::" << QString::fromLocal8Bit(opus_strerror(err));if (!g_decoder || err < 0) {qDebug() << "failed to create an encoder" << QString::fromLocal8Bit(opus_strerror(err));fprintf(stderr, "failed to create an encoder: %s\n", opus_strerror(err));if (!g_decoder) {opus_decoder_destroy(g_decoder);}return false;}return true;
}
保存文件:
QFile f("pcm.data");
bool bisOk = f.open(QIODevice::WriteOnly | QIODevice::Truncate);
if(bisOk2== true)
{qDebug() << "save file size=" << testpcm.size();f.write(testpcm.data(), testpcm.size());f.close();
}else{qDebug() << " pcm.data voice file open failed";
}
需要更详细的了解opus可以去官网了解 https://www.opus-codec.org/
另附笔者参考的demo
https://download.csdn.net/download/u011942101/16744647
Qt 之 opus编码相关推荐
- QT读写文本文件编码设置
QT读写文本文件编码设置 一.编码知识科普 Qt常见的两种编码是:UTF-8和GBK ★UTF-8:Unicode TransformationFormat-8bit,允许含BOM,但通常不含BOM. ...
- opus 编码和解码完整demo代码,opus和wav互转(js源码)
最近研究webRTC中的opus音频的解码.找了很多例子都不了.不是代码太老,就是运行环境有问题.因此,自己结合大神的示例,整理了编码和解码的完整demo源码. opus解码demo可将opus文件保 ...
- 在Android中实现OPUS编码
将PCM转换成OPUS编码 Opus是一个有损声音编码的格式,由Xiph.Org基金会开发,之后由IETF(互联网工程任务组)进行标准化,目标是希望用单一格式包含声音和语音,取代Speex和Vorbi ...
- Qt之QML编码约定
文章目录 概述 QML对象声明 分组属性 列表 JavaScript代码 概述 之前看到一篇Qt 官方的关于QML 开发的编码约定,这里做个简单的总结.一个良好的编码习惯可提高代码的可阅读性,并且整个 ...
- opus 压缩率_Opus从入门到精通(八)Opus编码基础之压缩编码
只有在保持信号质量的前提下,设法降低码率及数据量,才能使标准得到应用.而这种降低码率的过程,被称为压缩编码或新源编码. 这节介绍一些基础的压缩编码思想与方法,为后面Opus语音编码做基础准备. 压缩编 ...
- 关于QT TTS ( TextToSpeech ) 编码导致的只能读英文不能读中文的问题
要阅读的语音内容写在代码中时,编码不对会导致TTS只读英文,即使你设置了Voice为Chinese. 经过我的测试,需要使用"UTF-8"编码,并且BOM总是删除,才能同时阅读英文 ...
- QT乱码总结9.编码测试和总结四
QT乱码总结0.Qt乱码产生因素 https://blog.csdn.net/liujiayu2/article/details/103167953 QT乱码总结1.Unicode 和 UTF-8 h ...
- QT乱码总结8.编码测试和总结三
QT乱码总结0.Qt乱码产生因素 https://blog.csdn.net/liujiayu2/article/details/103167953 QT乱码总结1.Unicode 和 UTF-8 h ...
- QT乱码总结7.编码测试和总结二
QT乱码总结0.Qt乱码产生因素 https://blog.csdn.net/liujiayu2/article/details/103167953 QT乱码总结1.Unicode 和 UTF-8 h ...
最新文章
- Tensorflow基础入门十大操作总结
- 进行博客博文管理的设计
- xgboost与coo_matrix
- UML模型中的图-实现图【组件图、配置图】
- 为什么SpringBoot如此受欢迎,以及如何有效地学习SpringBoot?
- RobotFramework_4.SeleniumLibrary操作(二)
- python调用摄像头人脸识别代码_OpenCV3-Python人脸识别方法—人脸识别与标记
- Bzoj1176:MokiaCogs1752:[BOI2007]摩基亚Mokia
- 常用编程语言介绍和特点
- CRC32绕过RAR密码
- java英语面试自我介绍_java的英文面试自我介绍
- 大学生数码装备推荐,2022年值得入手的数码好物
- 两个onCreate()方法
- [pyecharts]如何使用Python将多个图表生成到一个HTML中?
- 取代房子,中国又一种资本在崛起
- 【Java】122. 买卖股票的最佳时机 II-----简单代码实现发杂问题
- 算法设计与分析——活动安排问题(Java)
- 6.tendermint默克尔树
- 利用ZjDroid对 捕鱼达人3 脱壳及破解过程
- encodeURI、encodeURIComponent的区别
热门文章
- 机房空调节能方案如何实施?
- FL Studio20.9中文语言水果软件
- word无法启动转换器mswrd632 wpc的几种解决方法
- Proteus存储器读写数字并在数码管显示
- JAVA圣诞代码_[Java教程]【Merry Christmas】圣诞节,给博客添加浪漫的下雪效果!...
- Away3D学习笔记(4)
- python爬取直播_python selenium爬取斗鱼所有直播房间信息过程详解
- 构造哈夫曼树(C语言)
- 克拉美罗界(CRLB)推导
- 通信网实验_DFS算法_Dijkstra算法_Mininet_Ryu