七、通过libfdk_aac编解码器实现aac音频和pcm的编解码

前言


测试环境:

  • ffmpeg的4.3.2自行编译版本
  • windows环境
  • qt5.12

AAC编码是MP3格式的后继产品,通常在相同的比特率下可以获得比MP3更高的声音质量,是iPhone、iPod、iPad、iTunes的标准音频格式。

AAC相较于MP3的改进包含:

  • 更多的采样率选择:8kHz ~ 96kHz,MP3为16kHz ~ 48kHz
  • 更高的声道数上限:48个,MP3在MPEG-1模式下为最多双声道,MPEG-2模式下5.1声道
  • 改进的压缩功能:以较小的文件大小提供更高的质量
  • 改进的解码效率:需要较少的处理能力进行解码

AAC编码为了使用不同场景的需求,设计了很多规格

  • MPEG-2 AAC LC:低复杂度规格(Low Complexity)
  • MPEG-2 AAC Main:主规格
  • MPEG-2 AAC SSR:可变采样率规格(Scaleable Sample Rate)
  • MPEG-4 AAC LC:低复杂度规格(Low Complexity)
    • 现在的手机比较常见的MP4文件中的音频部分使用了该规格
  • MPEG-4 AAC Main:主规格
  • MPEG-4 AAC SSR:可变采样率规格(Scaleable Sample Rate)
  • MPEG-4 AAC LTP:长时期预测规格(Long Term Predicition)
  • MPEG-4 AAC LD:低延迟规格(Low Delay)
  • MPEG-4 AAC HE:高效率规格(High Efficiency)

众多规格中只需关注LC和HE


pcm与aac的转换需要AAC编解码器(如下列举几种常用的AAC编解码器)

  • Nero AAC
    • 支持LC/HE规格
    • 目前已经停止开发维护
  • FFmpeg AAC
    • 支持LC规格
    • FFmpeg官方内置的AAC编解码器,在libavcodec库中
      • 编解码器名字叫做aac
      • 在开发过程中通过这个名字找到编解码器
  • FAAC(Freeware Advanced Audio Coder)
    • 支持LC规格
    • 可以集成到FFmpeg的libavcodec中
      • 编解码器名字叫做libfaac
      • 在开发过程中通过这个名字找到编解码器,最后调用FAAC库的功能
    • 从2016年开始,FFmpeg已经移除了对FAAC的支持
  • Fraunhofer FDK AAC
    • 支持LC/HE规格
    • 目前质量最高的AAC编解码器
    • 可以集成到FFmpeg的libavcodec中
      • 编解码器名字叫做libfdk_aac
      • 在开发过程中通过这个名字找到编解码器,最后调用FDK AAC库的功能

编码质量排名:Fraunhofer FDK AAC > FFmpeg AAC > FAAC。

由于libfdk_aac最好,但是网上下载好的ffmpeg编译好的版本不带libfdk_aac编解码器。所以我们只能自行编译ffmpeg。

如下命令可以查看FFmpeg目前集成的AAC编解码器

ffmpeg -codecs | findstr aac

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


自己手动编译FFmpeg源码,将libfdk_aac集成到FFmpeg中,这种方式最好,但在windows环境下较为麻烦。

因为编译源码需要在类Unix系统上的(Linux、Mac等),默认无法直接用在Windows上。所以必须先用MSYS2软件在Windows上模拟出Linux环境,然后再在其中用MinGW软件对FFmpeg进行编译。

链接:windows下msys2编译64位的ffmpeg源码


编译好源码后,需要把.pro文件配置成新编译的源码。

fdk-aac对需要编解码的pcm音频有一定的格式要求

  • 采样格式必须为16位整数PCM
  • 采样率只支持:8000、11025、12000、16000、22050、24000、32000、44100、48000、64000、88200、96000

命令行将pcm和wav文件编码成aac音频

# pcm -> aac
ffmpeg -ar 44100 -ac 2 -f s16le -i in.pcm -c:a libfdk_aac out.aac
-ar 44100 -ac 2 -f s16le   --PCM输入数据的参数
-c:a	 设置音频编码器,c表示codec(编解码器),a表示audio(音频)。 等价写法 -codec:a或-acodec# wav -> aac
ffmpeg -i in.wav -c:a libfdk_aac out.aac   

默认生成的aac文件是LC规格的。aac文件比之前的pcm文件小了很多很多。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

aac的缩写还可以是m4a和mp4。虽然现在都只认为mp4是视频文件


首先是pcm编码为aac

完整代码

AacEncodeThread.h

#ifndef AACENCODETHREAD_H
#define AACENCODETHREAD_H#include <QFile>
#include <QObject>
#include <QThread>extern "C" {
#include <libavformat/avformat.h>
}typedef struct {const char *filename;int sampleRate;AVSampleFormat sampleFmt;int chLayout;
} AudioEncodeSpec;class AacEncodeThread : public QThread
{Q_OBJECT
public:explicit AacEncodeThread(QObject *parent = nullptr);~AacEncodeThread();static int check_sample_fmt(const AVCodec *codec,enum AVSampleFormat sample_fmt);static int encode(AVCodecContext *ctx,AVFrame *frame,AVPacket *pkt,QFile &outFile);static void aacEncode(AudioEncodeSpec &in,const char *outFilename);signals:// QThread interface
protected:virtual void run() override;
};#endif // AACENCODETHREAD_H

AacEncodeThread.cpp

#include "aacencodethread.h"#include <QDebug>
#include <QFile>extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}#define ERROR_BUF(ret) \char errbuf[1024]; \av_strerror(ret, errbuf, sizeof (errbuf));AacEncodeThread::AacEncodeThread(QObject *parent) : QThread(parent)
{// 当监听到线程结束时(finished),就调用deleteLater回收内存connect(this, &AacEncodeThread::finished,this, &AacEncodeThread::deleteLater);
}AacEncodeThread::~AacEncodeThread()
{// 断开所有的连接disconnect();// 内存回收之前,正常结束线程requestInterruption();// 安全退出quit();wait();qDebug() << this << "析构(内存被回收)";
}// 检查采样格式
int AacEncodeThread::check_sample_fmt(const AVCodec *codec,enum AVSampleFormat sample_fmt) {const enum AVSampleFormat *p = codec->sample_fmts;while (*p != AV_SAMPLE_FMT_NONE) {
//        qDebug() << av_get_sample_fmt_name(*p);if (*p == sample_fmt) return 1;p++;}return 0;
}// 音频编码
// 返回负数:中途出现了错误
// 返回0:编码操作正常完成
int AacEncodeThread::AacEncodeThread::encode(AVCodecContext *ctx,AVFrame *frame,AVPacket *pkt,QFile &outFile) {// 发送数据到编码器int ret = avcodec_send_frame(ctx, frame);if (ret < 0) {ERROR_BUF(ret);qDebug() << "avcodec_send_frame error" << errbuf;return ret;}// 不断从编码器中取出编码后的数据// while (ret >= 0)while (true) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {// 继续读取数据到frame,然后送到编码器return 0;} else if (ret < 0) { // 其他错误return ret;}// 成功从编码器拿到编码后的数据// 将编码后的数据写入文件outFile.write((char *) pkt->data, pkt->size);// 释放pkt内部的资源av_packet_unref(pkt);}
}void AacEncodeThread::aacEncode(AudioEncodeSpec &in, const char *outFilename)
{// 文件QFile inFile(in.filename);QFile outFile(outFilename);// 返回结果int ret = 0;// 编码器AVCodec *codec = nullptr;// 编码上下文AVCodecContext *ctx = nullptr;// 存放编码前的数据(pcm)AVFrame *frame = nullptr;// 存放编码后的数据(aac)AVPacket *pkt = nullptr;// 获取编码器
//    codec = avcodec_find_encoder(AV_CODEC_ID_AAC);codec = avcodec_find_encoder_by_name("libfdk_aac");if (!codec) {qDebug() << "encoder not found";return;}// libfdk_aac对输入数据的要求:采样格式必须是16位整数// 检查输入数据的采样格式if (!check_sample_fmt(codec, in.sampleFmt)) {qDebug() << "unsupported sample format"<< av_get_sample_fmt_name(in.sampleFmt);return;}// 创建编码上下文ctx = avcodec_alloc_context3(codec);if (!ctx) {qDebug() << "avcodec_alloc_context3 error";return;}// 设置PCM参数ctx->sample_rate = in.sampleRate;ctx->sample_fmt = in.sampleFmt;ctx->channel_layout = in.chLayout;// 比特率ctx->bit_rate = 32000;// 规格ctx->profile = FF_PROFILE_AAC_HE_V2;// 打开编码器
//    AVDictionary *options = nullptr;
//    av_dict_set(&options, "vbr", "5", 0);
//    ret = avcodec_open2(ctx, codec, &options);ret = avcodec_open2(ctx, codec, nullptr);if (ret < 0) {ERROR_BUF(ret);qDebug() << "avcodec_open2 error" << errbuf;goto end;}// 创建AVFrameframe = av_frame_alloc();if (!frame) {qDebug() << "av_frame_alloc error";goto end;}// frame缓冲区中的样本帧数量(由ctx->frame_size决定)frame->nb_samples = ctx->frame_size;frame->format = ctx->sample_fmt;frame->channel_layout = ctx->channel_layout;// 利用nb_samples、format、channel_layout创建缓冲区ret = av_frame_get_buffer(frame, 0);if (ret < 0) {ERROR_BUF(ret);qDebug() << "av_frame_get_buffer error" << errbuf;goto end;}// 创建AVPacketpkt = av_packet_alloc();if (!pkt) {qDebug() << "av_packet_alloc error";goto end;}// 打开文件if (!inFile.open(QFile::ReadOnly)) {qDebug() << "file open error" << in.filename;goto end;}if (!outFile.open(QFile::WriteOnly)) {qDebug() << "file open error" << outFilename;goto end;}// 读取数据到frame中while ((ret = inFile.read((char *) frame->data[0],frame->linesize[0])) > 0) {// 从文件中读取的数据,不足以填满frame缓冲区if (ret < frame->linesize[0]) {int bytes = av_get_bytes_per_sample((AVSampleFormat) frame->format);int ch = av_get_channel_layout_nb_channels(frame->channel_layout);// 设置真正有效的样本帧数量// 防止编码器编码了一些冗余数据frame->nb_samples = ret / (bytes * ch);}// 进行编码if (encode(ctx, frame, pkt, outFile) < 0) {goto end;}}// 刷新缓冲区encode(ctx, nullptr, pkt, outFile);end:// 关闭文件inFile.close();outFile.close();// 释放资源av_frame_free(&frame);av_packet_free(&pkt);avcodec_free_context(&ctx);qDebug() << "线程正常结束";
}void AacEncodeThread::run()
{AudioEncodeSpec in;in.filename = "E:/media/test.pcm";in.sampleRate = 44100;in.sampleFmt = AV_SAMPLE_FMT_S16;in.chLayout = AV_CH_LAYOUT_STEREO;aacEncode(in, "E:/media/test.aac");
}

线程调用:

void MainWindow::on_pushButton_aac_encode_clicked()
{m_pAacEncodeThread=new AacEncodeThread(this);m_pAacEncodeThread->start();
}

注意:.h文件中提前声明了以下全局变量

AacEncodeThread *m_pAacEncodeThread=nullptr;


下面是aac解码成pcm

完整代码

AacDecodeThread.h

#ifndef AACDECODETHREAD_H
#define AACDECODETHREAD_H#include <QFile>
#include <QObject>
#include <QThread>extern "C" {
#include <libavformat/avformat.h>
}typedef struct {const char *filename;int sampleRate;AVSampleFormat sampleFmt;int chLayout;
} AudioDecodeSpec;class AacDecodeThread : public QThread
{Q_OBJECT
public:explicit AacDecodeThread(QObject *parent = nullptr);~AacDecodeThread();static int decode(AVCodecContext *ctx,AVPacket *pkt,AVFrame *frame,QFile &outFile);static void aacDecode(const char *inFilename,AudioDecodeSpec &out);signals:// QThread interface
protected:virtual void run() override;
};#endif // AACDECODETHREAD_H

AacDecodeThread.cpp

#include "aacdecodethread.h"#include <QDebug>extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}#define ERROR_BUF(ret) \char errbuf[1024]; \av_strerror(ret, errbuf, sizeof (errbuf));// 输入缓冲区的大小
#define IN_DATA_SIZE 20480
// 需要再次读取输入文件数据的阈值
#define REFILL_THRESH 4096AacDecodeThread::AacDecodeThread(QObject *parent) : QThread(parent)
{// 当监听到线程结束时(finished),就调用deleteLater回收内存connect(this, &AacDecodeThread::finished,this, &AacDecodeThread::deleteLater);
}AacDecodeThread::~AacDecodeThread()
{// 断开所有的连接disconnect();// 内存回收之前,正常结束线程requestInterruption();// 安全退出quit();wait();qDebug() << this << "析构(内存被回收)";
}int AacDecodeThread::decode(AVCodecContext *ctx,AVPacket *pkt,AVFrame *frame,QFile &outFile) {// 发送压缩数据到解码器int ret = avcodec_send_packet(ctx, pkt);if (ret < 0) {ERROR_BUF(ret);qDebug() << "avcodec_send_packet error" << errbuf;return ret;}while (true) {// 获取解码后的数据ret = avcodec_receive_frame(ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {ERROR_BUF(ret);qDebug() << "avcodec_receive_frame error" << errbuf;return ret;}//        for (int i = 0; i < frame->channels; i++) {
//            frame->data[i];
//        }// 将解码后的数据写入文件outFile.write((char *) frame->data[0], frame->linesize[0]);}
}void AacDecodeThread::aacDecode(const char *inFilename, AudioDecodeSpec &out)
{// 返回结果int ret = 0;// 用来存放读取的输入文件数据(aac)// 加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界char inDataArray[IN_DATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];char *inData = inDataArray;// 每次从输入文件中读取的长度(aac)int inLen;// 是否已经读取到了输入文件的尾部int inEnd = 0;// 文件QFile inFile(inFilename);QFile outFile(out.filename);// 解码器AVCodec *codec = nullptr;// 上下文AVCodecContext *ctx = nullptr;// 解析器上下文AVCodecParserContext *parserCtx = nullptr;// 存放解码前的数据(aac)AVPacket *pkt = nullptr;// 存放解码后的数据(pcm)AVFrame *frame = nullptr;// 获取解码器codec = avcodec_find_decoder_by_name("libfdk_aac");if (!codec) {qDebug() << "decoder not found";return;}// 初始化解析器上下文parserCtx = av_parser_init(codec->id);if (!parserCtx) {qDebug() << "av_parser_init error";return;}// 创建上下文ctx = avcodec_alloc_context3(codec);if (!ctx) {qDebug() << "avcodec_alloc_context3 error";goto end;}// 创建AVPacketpkt = av_packet_alloc();if (!pkt) {qDebug() << "av_packet_alloc error";goto end;}// 创建AVFrameframe = av_frame_alloc();if (!frame) {qDebug() << "av_frame_alloc error";goto end;}// 打开解码器ret = avcodec_open2(ctx, codec, nullptr);if (ret < 0) {ERROR_BUF(ret);qDebug() << "avcodec_open2 error" << errbuf;goto end;}// 打开文件if (!inFile.open(QFile::ReadOnly)) {qDebug() << "file open error:" << inFilename;goto end;}if (!outFile.open(QFile::WriteOnly)) {qDebug() << "file open error:" << out.filename;goto end;}while ((inLen = inFile.read(inDataArray, IN_DATA_SIZE)) > 0) {inData = inDataArray;while (inLen > 0) {// 经过解析器解析// 内部调用的核心逻辑是:ff_aac_ac3_parseret = av_parser_parse2(parserCtx, ctx,&pkt->data, &pkt->size,(uint8_t *) inData, inLen,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0) {ERROR_BUF(ret);qDebug() << "av_parser_parse2 error" << errbuf;goto end;}// 跳过已经解析过的数据inData += ret;// 减去已经解析过的数据大小inLen -= ret;// 解码if (pkt->size > 0 && decode(ctx, pkt, frame, outFile) < 0) {goto end;}}}decode(ctx, nullptr, frame, outFile);// 赋值输出参数out.sampleRate = ctx->sample_rate;out.sampleFmt = ctx->sample_fmt;out.chLayout = ctx->channel_layout;end:inFile.close();outFile.close();av_packet_free(&pkt);av_frame_free(&frame);av_parser_close(parserCtx);avcodec_free_context(&ctx);
}void AacDecodeThread::run()
{AudioDecodeSpec out;out.filename = "E:/media/test.pcm";aacDecode("E:/media/test.aac", out);qDebug() << "采样率:" << out.sampleRate;qDebug() << "采样格式:" << av_get_sample_fmt_name(out.sampleFmt);qDebug() << "声道数:" << av_get_channel_layout_nb_channels(out.chLayout);
}

注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用


码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/161328.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系列八、key是弱引用,gc垃圾回收时会影响ThreadLocal正常工作吗

一、key是弱引用&#xff0c;gc垃圾回收时会影响ThreadLocal正常工作吗 到这里&#xff0c;有些小伙伴可能有疑问&#xff0c;ThreadLocalMap的key既然是 弱引用&#xff0c;那么GC时会不会贸然地把key回收掉&#xff0c;进而影响ThreadLocal的正常使用呢&#xff1f;答案是不会…

HTML新手入门笔记整理:HTML基本标签

结构标签 <html> </html> 告诉浏览器这个页面是从<html> 开始&#xff0c;到 </html>结束 <head> </head> 网页的头部&#xff0c;用于定义一些特殊内容&#xff0c;如页面标题、定时刷新、外部文件等。 <body> </body> …

基于SSM的旅游管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

数据分析基础之《matplotlib(1)—介绍》

一、什么是matplotlib 1、专门用于开发2D图表&#xff08;包括3D图表&#xff09; 2、使用起来及其简单 3、以渐进、交互方式实现数据可视化 4、matplotlib mat&#xff1a;matrix&#xff08;矩阵&#xff09; plot&#xff1a;画图 lib&#xff1a;库 二、为什么要学习m…

记录一次因内存不足而导致hiveserver2和namenode进程宕机的排查

背景 最近发现集群主节点总有进程宕机&#xff0c;定位了大半天才找到原因&#xff0c;分享一下 排查过程 查询hiveserver2和namenode日志&#xff0c;都是正常的&#xff0c;突然日志就不记录了&#xff0c;直到我重启之后又恢复工作了。 排查各种日志都是正常的&#xff0…

代码规范之-理解ESLint、Prettier、EditorConfig

前言 团队多人协同开发项目&#xff0c;困扰团队管理的一个很大的问题就是&#xff1a;无可避免地会出现每个开发者编码习惯不同、代码风格迥异&#xff0c;为了代码高可用、可维护性&#xff0c;需要从项目管理上尽量统一和规范代码。理想的方式需要在项目工程化方面&#xff…

部署jekins遇到的问题

jdk问题 我用的jdk版本是21的结果版本太新了&#xff0c;启动jekins服务的时候总是报错最后在jekins的安装目录下面的jekinsErr.log查看日志发现是jdk问题最后换了一个17版本的就解决了。 unity和jekins jekins和Git源码管理 jekins和Git联动使用 我想让jekins每次打包的时…

3D电路板在线渲染案例

从概念上讲,这是有道理的,因为PCB印制电路板上的走线从一个连接到下一个连接的路线基本上是平面的。 然而,我们生活在一个 3 维世界中,能够以这种方式可视化电路以及相应的组件,对于设计过程很有帮助。本文将介绍KiCad中基本的3D查看功能,以及如何使用NSDT 3DConvert在线…

Day38力扣打卡

打卡记录 网格中的最小路径代价&#xff08;动态规划&#xff09; 链接 class Solution:def minPathCost(self, grid: List[List[int]], moveCost: List[List[int]]) -> int:m, n len(grid), len(grid[0])f [[0x3f3f3f3f3f] * n for _ in range(m)]f[0] grid[0]for i i…

Sentinel 授权规则 (AuthorityRule)

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 SpringbootDubboNacos 集成 Sentinel&…

Unity 三维场景的搭建 软件构造实验报告

实验2&#xff1a;仿真系统功能实现 1.实验目的 &#xff08;1&#xff09;熟悉在Unity中设置仿真场景&#xff1b; &#xff08;2&#xff09;熟悉在Unity中C#语言的使用&#xff1b; &#xff08;3&#xff09;熟悉仿真功能的实现。 2.实验内容 新建一个仿真场景&#x…

SpringBoot_websocket实战

SpringBoot_websocket实战 前言1.websocket入门1.1 websocket最小化配置1.1.1 后端配置1.1.2 前端配置 1.2 websocket使用sockjs1.2.1 后端配置1.2.2 前端配置 1.3 websocket使用stomp协议1.3.1 后端配置1.3.2 前端配置 2.websocket进阶2.1 websocket与stomp有什么区别2.2 webs…

思维模型 重叠效应

本系列文章 主要是 分享 思维模型 &#xff0c;涉及各个领域&#xff0c;重在提升认知。相似内容易被混淆或遗忘。 1 重叠效应的应用 1.1 重叠效应在教育中的应用 1 通过避免重叠效应提升学习效率 为了避免重叠效应&#xff0c;通过对比、归纳等方法来帮助学生更好地理解和掌…

黑马React18: Redux

黑马React: Redux Date: November 19, 2023 Sum: Redux基础、Redux工具、调试、美团案例 Redux介绍 Redux 是React最常用的集中状态管理工具&#xff0c;类似于Vue中的Pinia&#xff08;Vuex&#xff09;&#xff0c;可以独立于框架运行 作用&#xff1a;通过集中管理的方式管…

VPS配置了swap没发挥作用怎么办

1 swap配置了但没用上 我的服务器内存是2G&#xff0c;装多一点东西就不够用&#xff0c;于是我给他分配了2G的swap&#xff0c;等了几小时&#xff0c;swap还是一点都没有使用 Linux中Swap&#xff08;即&#xff1a;交换分区&#xff09;&#xff0c;类似于Windows的虚拟内存…

MongoDB的常用操作以及python连接MongoDB

一,MongoDB的启动 mongod --dbpath..\data\db mongodb注意同时开两个窗口&#xff0c;不要关&#xff01; 二, MongoDB的简单使用 简单介绍一下mongoDB中一些操作 show dbs: 显示所有数据库 show databases: 显示所有数据库 use xxxx: 使用指定数据库/创建数据库&#xff08…

时序预测 | MATLAB实现基于BiLSTM-AdaBoost双向长短期记忆网络结合AdaBoost时间序列预测

时序预测 | MATLAB实现基于BiLSTM-AdaBoost双向长短期记忆网络结合AdaBoost时间序列预测 目录 时序预测 | MATLAB实现基于BiLSTM-AdaBoost双向长短期记忆网络结合AdaBoost时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.Matlab实现BiLSTM-Adaboost…

【精选】Ajax技术知识点合集

Ajax技术详解 Ajax简介 Ajax 即“Asynchronous Javascript And XML”&#xff08;异步 JavaScript 和 XML&#xff09;&#xff0c;是指一种创建 交互式、快速动态应用的网页开发技术&#xff0c;无需重新加载整个网页的情况下&#xff0c;能够更新页面局 部数据的技术。通过在…

Scala如何写一个通用的游戏数据爬虫程序

以前想要获取一些网站数据的时候&#xff0c;都是通过人工手动复制粘贴&#xff0c;这样的效率及其低下。数据少无所谓&#xff0c;如果需要采集大量数据&#xff0c;手动就显得乏力了。半夜睡不着&#xff0c;爬起来写一段有关游戏商品数据的爬虫通用模板&#xff0c;希望能帮…

专业pdf编辑工具PDF Expert mac中文版特点介绍

PDF Expert mac是一款专业的PDF编辑和阅读工具。它可以帮助用户在Mac、iPad和iPhone等设备上查看、注释、编辑、填写和签署PDF文档。 PDF Expert mac软件特点 PDF编辑&#xff1a;PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动、旋转、缩放、裁剪等操作…