七、通過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.pswp.cn/news/161328.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/161328.shtml
英文地址,請注明出處:http://en.pswp.cn/news/161328.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【leetcode】209. 長度最小的子數組

209. 長度最小的子數組 - 力扣&#xff08;LeetCode&#xff09; 給定一個含有 n 個正整數的數組和一個正整數 target 。 找出該數組中滿足其總和大于等于 target 的長度最小的 連續子數組 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其長度。如果不存在符合條…

系列八、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…

大文件導出

關于大文件導出的優化迭代情況如下&#xff1a; 計算機配置&#xff1a;四核16G內存 初始版本為單線程單文件導出文件&#xff0c;mybatis讀 opencsv寫&#xff0c;耗時將近三小時&#xff1b; 第一輪優化改為多線程單文件&#xff0c;提高讀數據效率&#xff0c;時間僅縮減十分…

數據分析基礎之《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…

vue3 + vue-router + keep-alive緩存頁面

1.vue-router中增加mate.keepAlive和deepth屬性 {path: /,name: home,component: HomeView,meta: {// 當前頁面要不要緩存keepAlive: false,// 當前頁面層級deepth: 1,}},{path: /list,name: list,component: ListView,meta: {// 當前頁面要不要緩存keepAlive: true,// 當前頁…

代碼規范之-理解ESLint、Prettier、EditorConfig

前言 團隊多人協同開發項目&#xff0c;困擾團隊管理的一個很大的問題就是&#xff1a;無可避免地會出現每個開發者編碼習慣不同、代碼風格迥異&#xff0c;為了代碼高可用、可維護性&#xff0c;需要從項目管理上盡量統一和規范代碼。理想的方式需要在項目工程化方面&#xff…

Kafka官方生產者和消費者腳本簡單使用

問題 怎樣使用Kafka官方生產者和消費者腳本進行消費生產和消費?這里假設已經下載了kafka官方文件,并已經解壓. 生產者配置文件 producer_hr.properties bootstrap.servers10.xx.xx.xxx:9092,10.xx.xx.xxx:9092,10.xx.xx.xxx:9092 compression.typenone security.protocolS…

部署jekins遇到的問題

jdk問題 我用的jdk版本是21的結果版本太新了&#xff0c;啟動jekins服務的時候總是報錯最后在jekins的安裝目錄下面的jekinsErr.log查看日志發現是jdk問題最后換了一個17版本的就解決了。 unity和jekins jekins和Git源碼管理 jekins和Git聯動使用 我想讓jekins每次打包的時…

【css/vue】使用css變量,在同一個頁面根據不同情況改變字號等樣式

解決方法是&#xff1a;將 css 的屬性使用 v-bind 與 Vue 組件的屬性綁定&#xff0c;當組件的屬性變化時&#xff0c;css 對應的屬性值也就會隨之變化&#xff1b; 具體實現代碼&#xff1a; <template><div><span class"navTitle">標題名</s…

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…

【洛谷 B2010】帶余除法 題解(順序結構+四則運算)

帶余除法 題目描述 給定被除數和除數&#xff0c;求整數商及余數。此題中請使用默認的整除和取余運算&#xff0c;無需對結果進行任何特殊處理。 輸入格式 一行&#xff0c;包含兩個整數&#xff0c;依次為被除數和除數&#xff08;除數非零&#xff09;&#xff0c;中間用…

Sentinel 授權規則 (AuthorityRule)

Sentinel 是面向分布式、多語言異構化服務架構的流量治理組件&#xff0c;主要以流量為切入點&#xff0c;從流量路由、流量控制、流量整形、熔斷降級、系統自適應過載保護、熱點流量防護等多個維度來幫助開發者保障微服務的穩定性。 SpringbootDubboNacos 集成 Sentinel&…

一分鐘快速了解Python3.12新特性

Python 3.12&#xff0c;作為Python編程語言的最新穩定版&#xff0c;引入了一系列對語言和標準庫的改變&#xff0c;發布于2023年10月2日。重點變化包括&#xff1a; 新語法特性: PEP 695 引入類型形參語法和 type 語句&#xff0c;允許創建更明確的泛型類和函數。PEP 701 改進…

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;通過對比、歸納等方法來幫助學生更好地理解和掌…