SILK SDK + Qt 将QQ/微信的 silk/amr 音频转为 wav 格式

2023-10-20 23:30

本文主要是介绍SILK SDK + Qt 将QQ/微信的 silk/amr 音频转为 wav 格式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0.前言

SILK 编码最早在 Skype 中使用,它在编码效率和质量之间取得了很好的平衡,因此被广泛应用在互联网的音频相关产品中。SILK 的最新版本是 2012 年发布的 SDK 1.0.9,即SILK V3。

腾讯系产品,包括QQ、微信、小程序,在语音相关的实现中,也大量使用到 SILK 编码,不过他在标准的 SILK 文件头加了一个字节的 0x02 ,所以在解码的时候要多判断一次。虽然腾讯的音频文件格式后缀也可能叫 AMR,不过并不是真的 AMR格式,还是 SILK 格式的。

下图是一个微信 AMR 文件的文件头,以 0x02 开头,然后是九个固定字符: #!SILK_V3,接下来就是语音数据。

还要注意的是 SILK 编码的采样频率和码率是自适应的,参见 翻译的文档。

WAV 格式就相对方便点,可以直接存放 PCM 数据,将 SLIK 解码为 PCM 后加一个 WAV 头就成了一个 wav 音频文件( WAV 头的格式参考 WAV格式分析 )。

参考示例:SILK_SDK_SRC_v1.0.9\SILK_SDK_SRC_ARM_v1.0.9\test\Decoder.c

参考博客(转载):https://blog.csdn.net/weixin_34292924/article/details/87987436

参考博客:https://www.cnblogs.com/protosec/p/11673358.html

参考博客:https://blog.csdn.net/wanggp_2007/article/details/5536818

参考博客(翻译):https://blog.csdn.net/zhaosipei/article/details/7467810

别人fork的SDK源码:https://gitee.com/alvis/SILK_SDK_SRC

别人做的decoder:https://github.com/kn007/silk-v3-decoder

1.实现

下载好 SILK SDK 后,可以直接用 VS 打开工程文件进行编译为静态库,然后将 interface 文件夹和库文件导入到我们的应用工程中。也可以自己建一个 DLL 工程把那些原文件 copy 过去,然后导出 SKP_Silk_SDK_API.h 文件中的函数接口,生成动态或者静态库。

借助 SLIK 库,解码主要有两步,初始化解码器+循环解码为 PCM 数据。

转为 WAV 也两步,生成文件头+写入 PCM 数据。

代码链接(github):https://github.com/gongjianbo/MyTestCode/tree/master/Qt/SilkToWav

工程链接(CSDN):https://download.csdn.net/download/gongjianbo1992/13206416

主要代码:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE/*** @brief wav文件头结构体* @author 龚建波* @date 2020-11-12* @details* wav头是不定长格式,不过这里用的比较简单的格式* (数值以小端存储,不过pc一般是小端存储,暂不特殊处理)* 参照:https://www.cnblogs.com/ranson7zop/p/7657874.html* 参照:https://www.cnblogs.com/Ph-one/p/6839892.html*/
struct EasyWavHead
{char riffFlag[4]; //文档标识,大写"RIFF"//从下一个字段首地址开始到文件末尾的总字节数。//该字段的数值加 8 为当前文件的实际长度。unsigned int riffSize; //数据长度char waveFlag[4]; //文件格式标识,大写"WAVE"char fmtFlag[4]; //格式块标识,小写"fmt "unsigned int fmtSize; //格式块长度,可以是 16、 18 、20、40 等unsigned short compressionCode; //编码格式代码,1为pcmunsigned short numChannels; //通道个数unsigned int sampleRate; //采样频率//该数值为:声道数×采样频率×每样本的数据位数/8。//播放软件利用此值可以估计缓冲区的大小。unsigned int bytesPerSecond; //码率(数据传输速率)//采样帧大小。该数值为:声道数×位数/8。//播放软件需要一次处理多个该值大小的字节数据,用该数值调整缓冲区。unsigned short blockAlign; //数据块对其单位//存储每个采样值所用的二进制数位数。常见的位数有 4、8、12、16、24、32unsigned short bitsPerSample; //采样位数(采样深度)char dataFlag[4]; //表示数据开头,小写"data"unsigned int dataSize; //数据部分的长度
};class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();void paintEvent(QPaintEvent *event) override;//silk解码为pcmvoid decodeSilk(const QString &filepath);//生成wav(pcm)文件头信息//sampleRate: 采样频率//dataSize: pcm数据字节长度//return EasyWavHead: wav头static EasyWavHead createWavHead(int sampleRate,unsigned int dataSize);private:Ui::MainWindow *ui;QByteArray pcmData;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QFileDialog>
#include <QFile>
#include <QTime>
#include <QPainter>
#include <QDebug>#include "SKP_Silk_SDK_API.h"#define MAX_BYTES_PER_FRAME     1024
#define MAX_INPUT_FRAMES        5
#define MAX_FRAME_LENGTH        480
#define FRAME_LENGTH_MS         20
#define MAX_API_FS_KHZ          48MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);ui->lineEdit->setText(qApp->applicationDirPath()+"/weixin.amr");//选择文件connect(ui->btnFile,&QPushButton::clicked,[=]{const QString filepath=QFileDialog::getOpenFileName(this);if(!filepath.isEmpty())ui->lineEdit->setText(filepath);});//解码connect(ui->btnDecode,&QPushButton::clicked,[=]{const QString filepath=ui->lineEdit->text();if(filepath.isEmpty())return;qDebug()<<"---------------------------------";qDebug()<<"sdk version"<<SKP_Silk_SDK_get_version();qDebug()<<"begin decode"<<QTime::currentTime();decodeSilk(filepath);qDebug()<<"end decode"<<QTime::currentTime();qDebug()<<"data size"<<pcmData.size();update();});
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);QPainter painter(this);if(pcmData.count()<4)return;painter.translate(0,height()/2);const int length=pcmData.count()/2;const short *datas=(const short *)pcmData.constData();//点的x间隔double xspace=width()/(double)length;//绘制采样点步进,测试用的固定值,文件比较大懒得算const int step=1;//qDebug()<<"step"<<step;for(int i=0;i<length-step;i+=step){painter.drawLine(xspace*i,-datas[i]/150,xspace*(i+step),-datas[i+step]/150);}
}void MainWindow::decodeSilk(const QString &filepath)
{//SDK地址:https://gitee.com/alvis/SILK_SDK_SRC//参照示例:SILK_SDK_SRC_v1.0.9\SILK_SDK_SRC_ARM_v1.0.9\test\Decoder.c//参照博客:https://blog.csdn.net/weixin_34292924/article/details/87987436QFile file(filepath);qDebug()<<"Filepath"<<filepath<<"exists"<<file.exists();if(file.size()<10||!file.open(QIODevice::ReadOnly))return;//先判断silk头//#!SILK_V3QByteArray read_temp=file.read(1);if(read_temp=="#"){read_temp+=file.read(8);}else{//微信的silk前面加个一个字节的0x02read_temp=file.read(9);}if(read_temp!="#!SILK_V3"){file.close();return;}SKP_SILK_SDK_DecControlStruct dec_ctrl;//采样率,可作为参数传入//Valid values are 8000,12000, 16000, 24000, 32000, 44100, and 48000.const int sample_rate=8000;dec_ctrl.API_sampleRate = sample_rate;dec_ctrl.framesPerPacket = 1;//创建解码器SKP_int32 dec_size;int ret = SKP_Silk_SDK_Get_Decoder_Size(&dec_size);qDebug()<<"SKP_Silk_SDK_Get_Decoder_Size return"<<ret<<"size"<<dec_size;SKP_uint8 *dec_state=new SKP_uint8[dec_size];//初始化解码器ret=SKP_Silk_SDK_InitDecoder(dec_state);qDebug()<<"SKP_Silk_SDK_InitDecoder return"<<ret;//默认解出来貌似是16bit的精度SKP_uint8 payload[MAX_BYTES_PER_FRAME * MAX_INPUT_FRAMES], *payload_ptr = NULL;SKP_int16 out[((FRAME_LENGTH_MS * MAX_API_FS_KHZ) << 1) * MAX_INPUT_FRAMES], *out_ptr = NULL;SKP_int16 n_bytes=0;SKP_int32 read_counter=0;SKP_int16 len=0;SKP_int16 total_len=0;SKP_int32 frames=0;pcmData.clear();//循环读取并解码while (true) {//读取有效数据大小read_counter=file.read((char*)&n_bytes,sizeof(SKP_int16));if(n_bytes<1||read_counter<1)break;//读取有效数据read_counter=file.read((char*)payload,n_bytes);//qDebug()<<"read_counter"<<read_counter<<n_bytes<<QByteArray((char*)dec_state,sizeof(SKP_uint8)).toHex();if( (SKP_int16)read_counter < n_bytes ) {break;}payload_ptr=payload;out_ptr=out;total_len = 0;frames = 0;do {//解码ret = SKP_Silk_SDK_Decode(dec_state, &dec_ctrl, 0, payload_ptr, n_bytes, out_ptr, &len);if (ret) {qDebug()<<"SKP_Silk_SDK_Decode returned"<<ret;}frames++;out_ptr += len;total_len += len;if (frames > MAX_INPUT_FRAMES) {qDebug()<<"frames > MAX_INPUT_FRAMES"<<frames<<total_len;out_ptr = out;total_len = 0;frames = 0;}} while (dec_ctrl.moreInternalDecoderFrames);//将解码后的数据pcm保存pcmData.append((const char *)out,total_len*2);}//清理file.close();delete[]dec_state;//以wav(pcm s16)格式写入file.setFileName(filepath+".pcm.wav");if(file.open(QIODevice::WriteOnly)){//自定义的wav头结构体EasyWavHead head=createWavHead(sample_rate,pcmData.count());file.write((const char*)&head,sizeof(head));file.write(pcmData);file.close();}
}EasyWavHead MainWindow::createWavHead(int sampleRate, unsigned int dataSize)
{//默认貌似是16位精度,单通道const int bits=16;const int channels=1;const int head_size = sizeof(EasyWavHead);EasyWavHead wav_head;memset(&wav_head, 0, head_size);memcpy(wav_head.riffFlag, "RIFF", 4);memcpy(wav_head.waveFlag, "WAVE", 4);memcpy(wav_head.fmtFlag, "fmt ", 4);memcpy(wav_head.dataFlag, "data", 4);//出去头部前8个字节的长度,用的44字节的格式头,所以+44-8=36wav_head.riffSize = dataSize + 36;//不知道干嘛的wav_head.fmtSize = 16;//1为pcmwav_head.compressionCode = 0x01;wav_head.numChannels = channels;wav_head.sampleRate = sampleRate;wav_head.bytesPerSecond = (bits / 8) * channels * sampleRate;wav_head.blockAlign = (bits / 8) * channels;wav_head.bitsPerSample = bits;//除去头的数据长度wav_head.dataSize = dataSize;return wav_head;
}

 

这篇关于SILK SDK + Qt 将QQ/微信的 silk/amr 音频转为 wav 格式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/250399

相关文章

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

python与QT联合的详细步骤记录

《python与QT联合的详细步骤记录》:本文主要介绍python与QT联合的详细步骤,文章还展示了如何在Python中调用QT的.ui文件来实现GUI界面,并介绍了多窗口的应用,文中通过代码介绍... 目录一、文章简介二、安装pyqt5三、GUI页面设计四、python的使用python文件创建pytho

QT实现TCP客户端自动连接

《QT实现TCP客户端自动连接》这篇文章主要为大家详细介绍了QT中一个TCP客户端自动连接的测试模型,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录版本 1:没有取消按钮 测试效果测试代码版本 2:有取消按钮测试效果测试代码版本 1:没有取消按钮 测试效果缺陷:无法手动停

基于Qt实现系统主题感知功能

《基于Qt实现系统主题感知功能》在现代桌面应用程序开发中,系统主题感知是一项重要的功能,它使得应用程序能够根据用户的系统主题设置(如深色模式或浅色模式)自动调整其外观,Qt作为一个跨平台的C++图形用... 目录【正文开始】一、使用效果二、系统主题感知助手类(SystemThemeHelper)三、实现细节

Qt实现文件的压缩和解压缩操作

《Qt实现文件的压缩和解压缩操作》这篇文章主要为大家详细介绍了如何使用Qt库中的QZipReader和QZipWriter实现文件的压缩和解压缩功能,文中的示例代码简洁易懂,需要的可以参考一下... 目录一、实现方式二、具体步骤1、在.pro文件中添加模块gui-private2、通过QObject方式创建

Qt QWidget实现图片旋转动画

《QtQWidget实现图片旋转动画》这篇文章主要为大家详细介绍了如何使用了Qt和QWidget实现图片旋转动画效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、效果展示二、源码分享本例程通过QGraphicsView实现svg格式图片旋转。.hpjavascript