ESP32 I2S音頻總線學習筆記(四): INMP441采集音頻并實時播放

簡介

前面兩期文章我們介紹了I2S的讀取和寫入,一個是通過INMP441麥克風模塊采集音頻,一個是通過PCM5102A模塊播放音頻,那如果我們將兩者結合起來,將麥克風采集到的音頻通過PCM5102A播放,是不是就可以做一個擴音器了呢,本篇將介紹一個INMP441采集音頻并實時播放的應用。

往期相關文章:

ESP32 I2S音頻總線學習筆記(一):初識I2S通信與配置基礎

ESP32 I2S音頻總線學習筆記(二):I2S讀取INMP441音頻數據

ESP32 I2S音頻總線學習筆記(三):I2S音頻輸出

主要硬件

INMP441全向麥克風模塊:
在這里插入圖片描述
PCM5102A 立體聲DAC模塊 :在這里插入圖片描述

硬件接線

ESP32和麥克風INMP441:

ESP32INMP441
D13SCK
D12WS
D14SD
3.3VVDD
GNDGND

ESP32和PCM5102A:

ESP32PCM5102A
-VCC
3.3V3.3V
GNDGND
GNDFLT、DMP、SCL (這里SCL懸空可能會有干擾,所以接地)
D27BCK
D25DIN
D26LCK
GNDFMT
3.3VXMT

軟件實現

前面兩篇文章我們詳細介紹了I2S讀取和I2S輸出的初始化步驟,所以本篇我們就不過多介紹了。我們的目的是實現INMP441采集音頻并通過PCM5102A實時播放(可替換為其他DAC模塊如MAX98357),使用的協議是I2S,所以首先要分別配置麥克風I2S初始化音頻播放模塊I2S初始化,前者我們起名為setupI2SMic( ) ,后者起名為setupI2SDac( );因為我們是循環采集音頻,所以音頻處理邏輯部分放在loop( )函數,首先搭建起整體框架,注意必不可少的是包含I2S驅動頭文件:

#include <driver/i2s.h>void setup() {Serial.begin(115200);setupI2SMic();setupI2SDac();
}void loop()
{/*音頻處理邏輯部分...待補充*/
}

搭建完整體框架后,我們再來完善setupI2SMic( )和setupI2SDac( )里面的內容。

INMP441讀取 I2S初始化

setupI2SMic( )里面的I2S初始化步驟如何配置,可參考往期第二篇文章。這里因為使用到了兩個I2S,一個用于麥克風采集,一個用于播放音頻,所以我們將I2S0用于麥克風,I2S1用于音頻的實時播放。

// 配置 I2S0 用于麥克風采集
#define I2S_MIC_NUM    I2S_NUM_0
#define I2S_MIC_BCK 13
#define I2S_MIC_WS  12
#define I2S_MIC_SD  14#define SAMPLE_RATE    44100void setupI2SMic() {i2s_config_t mic_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 單聲道.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,  // 增加 DMA 緩沖區數量.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t mic_pin_config = {.bck_io_num = I2S_MIC_BCK,.ws_io_num = I2S_MIC_WS,.data_out_num = -1,.data_in_num = I2S_MIC_SD};i2s_driver_install(I2S_MIC_NUM, &mic_config, 0, NULL);i2s_set_pin(I2S_MIC_NUM, &mic_pin_config);
}

PCM5102A輸出 I2S初始化

// 配置 I2S1 用于 DAC 輸出
#define I2S_DAC_NUM    I2S_NUM_1
#define I2S_DAC_BCK 27
#define I2S_DAC_WS  26
#define I2S_DAC_DIN  25void setupI2SDac() {i2s_config_t dac_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 立體聲.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t dac_pin_config = {.bck_io_num = I2S_DAC_BCK,.ws_io_num = I2S_DAC_WS,.data_out_num = I2S_DAC_DIN,.data_in_num = -1};i2s_driver_install(I2S_DAC_NUM, &dac_config, 0, NULL);i2s_set_pin(I2S_DAC_NUM, &dac_pin_config);
}

音頻處理邏輯部分

音頻處理邏輯部分主要步驟是從麥克風采集數據,然后播放音頻數據,需要用到前面講過的兩個函數:esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);esp_err_t i2s_write(i2s_port_t i2s_num, const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait);
除此之外,因為麥克風是單聲道的,音頻播放模塊是雙聲道的,需要將單聲道轉化為雙聲道音頻;并且這里測試發現如果不對采集到的聲音進行放大,聲音是比較小的,所以還需進行音頻增益處理。其處理邏輯如下:

void loop(){/*音頻處理邏輯部分 *///      從麥克風采集數據//     增益處理,放大音量并限制范圍//     單聲道轉立體聲//     播放音頻數據
}

首先定義兩個音頻緩沖區,buffer 用于單聲道輸入,stereo_buffer 用于立體聲輸出。BUFFER_SIZE定義為單聲道緩沖區大小1024,每次讀取1024個樣本,雙聲道緩存區的樣本數是單聲道緩存區樣本數的兩倍,所以為BUFFER_SIZE * 2。

#define BUFFER_SIZE    1024int16_t buffer[BUFFER_SIZE];          // 單聲道緩沖區
int16_t stereo_buffer[BUFFER_SIZE * 2]; // 立體聲緩沖區

然后從麥克風采集數據:

i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);

將采集到的音頻數據進行增益處理,這里實測增益因子20~50比較合適,太大了采集到的音頻聲音會大,但是近距離說話的時候會容易出現破音,對遠處的采集聲音較好。溢出保護是因為16 位音頻范圍為 -32768 到 32767,放大后若超出此范圍就會導致失真。

 for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * 20;  // 增益因子為20,可以根據需要調整// 增加溢出保護if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;}

放大音頻數據后將單聲道音頻數據轉化為立體聲 音頻數據,單聲道只有一個聲道的數據,立體聲需要左右兩個聲道,這里將單聲道樣本復制到左右聲道。

 for (int i = 0; i < BUFFER_SIZE; i++) {stereo_buffer[2 * i] = buffer[i];      // 左聲道stereo_buffer[2 * i + 1] = buffer[i]; // 右聲道}

通過 I2S1 接口將立體聲數據發送到PCM5102A播放。

i2s_write(I2S_DAC_NUM, stereo_buffer, sizeof(stereo_buffer), &bytesWritten, portMAX_DELAY);

音頻處理邏輯部分的代碼如下,這個步驟可以總結為:1. 采集單聲道音頻 → 2. 放大音量并限制范圍 → 3. 轉換為立體聲 → 4. 播放

void loop() {int16_t buffer[BUFFER_SIZE];          // 單聲道緩沖區int16_t stereo_buffer[BUFFER_SIZE * 2]; // 立體聲緩沖區size_t bytesRead, bytesWritten;// 從麥克風采集數據i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);// 增益處理,放大音量并限制范圍for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * 20;  // 增益因子為20,可以根據需要調整// 增加溢出保護if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;}// 單聲道轉立體聲for (int i = 0; i < BUFFER_SIZE; i++) {stereo_buffer[2 * i] = buffer[i];      // 左聲道stereo_buffer[2 * i + 1] = buffer[i]; // 右聲道}// 播放音頻數據i2s_write(I2S_DAC_NUM, stereo_buffer, sizeof(stereo_buffer), &bytesWritten, portMAX_DELAY);
}

全部整合后的代碼如下:

#include <driver/i2s.h>// 配置 I2S0 用于麥克風采集
#define I2S_MIC_NUM    I2S_NUM_0
#define I2S_MIC_BCK 13
#define I2S_MIC_WS  12
#define I2S_MIC_SD  14// 配置 I2S1 用于 DAC 輸出
#define I2S_DAC_NUM    I2S_NUM_1
#define I2S_DAC_BCK 27
#define I2S_DAC_WS  26
#define I2S_DAC_DIN  25#define SAMPLE_RATE    44100
#define BUFFER_SIZE    1024void setupI2SMic() {i2s_config_t mic_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 單聲道.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,  // 增加 DMA 緩沖區數量.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t mic_pin_config = {.bck_io_num = I2S_MIC_BCK,.ws_io_num = I2S_MIC_WS,.data_out_num = -1,.data_in_num = I2S_MIC_SD};i2s_driver_install(I2S_MIC_NUM, &mic_config, 0, NULL);i2s_set_pin(I2S_MIC_NUM, &mic_pin_config);
}void setupI2SDac() {i2s_config_t dac_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 立體聲.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t dac_pin_config = {.bck_io_num = I2S_DAC_BCK,.ws_io_num = I2S_DAC_WS,.data_out_num = I2S_DAC_DIN,.data_in_num = -1};i2s_driver_install(I2S_DAC_NUM, &dac_config, 0, NULL);i2s_set_pin(I2S_DAC_NUM, &dac_pin_config);
}void setup() {Serial.begin(115200);setupI2SMic();setupI2SDac();
}void loop() {int16_t buffer[BUFFER_SIZE];          // 單聲道緩沖區int16_t stereo_buffer[BUFFER_SIZE * 2]; // 立體聲緩沖區size_t bytesRead, bytesWritten;// 從麥克風采集數據i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);// 增益處理,放大音量并限制范圍for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * 20;  // 增益因子為20,可以根據需要調整// 增加溢出保護if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;// }// 單聲道轉立體聲for (int i = 0; i < BUFFER_SIZE; i++) {stereo_buffer[2 * i] = buffer[i];      // 左聲道stereo_buffer[2 * i + 1] = buffer[i]; // 右聲道}// 播放音頻數據i2s_write(I2S_DAC_NUM, stereo_buffer, sizeof(stereo_buffer), &bytesWritten, portMAX_DELAY);
}

功能擴展

添加兩個按鈕,用于控制實時播放的音量。主要實現思路是通過按鈕去控制增益因子的增加或減少。

首先創建一個數組用來存儲增益因子,這里我們選擇放大的倍數為20, 30, 40, 50, 60, 70, 80可選,然后numGains用于計算元素個數,方便我們后續的判斷。并設置默認增益為20。

// 增益因子數組
const int gainFactors[] = {20, 30, 40, 50, 60, 70, 80};
const int numGains = sizeof(gainFactors) / sizeof(gainFactors[0]);
int currentGainIndex = 0;  // 默認增益為 20

整體思路是:如果按鍵1按下,currentGainIndex++, 如果按鍵2按下,currentGainIndex- -

考慮到按鍵抖動的情況,代碼如下:

// 按鍵引腳
#define BUTTON_UP_PIN 33   // 增益增加按鍵(GPIO 33)
#define BUTTON_DOWN_PIN 32 // 增益減少按鍵(GPIO 32)// 按鍵去抖變量
unsigned long lastUpDebounceTime = 0;
unsigned long lastDownDebounceTime = 0;
const unsigned long debounceDelay = 50;void KeyUp() {// 檢查增益增加按鍵bool upReading= digitalRead(BUTTON_UP_PIN);if (upReading!= lastUpButtonState) {lastUpDebounceTime = millis();}if ((millis() - lastUpDebounceTime) > debounceDelay) {if (upReading== LOW && currentGainIndex < numGains - 1) {  // 按下且未達最大增益currentGainIndex++;Serial.println("增益切換至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切換}}lastUpButtonState = upReading;
}void KeyDn() {// 檢查增益減少按鍵bool downReading = digitalRead(BUTTON_DOWN_PIN);if (downReading != lastDownButtonState) {lastDownDebounceTime = millis();}if ((millis() - lastDownDebounceTime) > debounceDelay) {if (downReading == LOW && currentGainIndex > 0) {  // 按下且未達最小增益currentGainIndex--;Serial.println("增益切換至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切換}}lastDownButtonState = downReading;}

增加增益因子和減少增益因子的去抖機制相同,以增加為例,機械按鍵在按下或釋放的時候會有產生短暫的電平抖動,通過去抖機制,等待一段時間(這里的debounceDelay)可以確保狀態穩定,避免誤判為多次按鍵事件。millis() 這個函數會返回程序啟動后經過的毫秒數。lastUpDebounceTime = millis(); 標記按鍵狀態變化的時間點,millis() - lastUpDebounceTime是當前時間與按鍵狀態變化時間的差值,表示從上次狀態變化以來經過的毫秒數, 用于判斷按鍵狀態是否穩定足夠長時間,如果時間差值大于 debounceDelay,說明按鍵狀態已經穩定,其效果類似于delay(50) (但是一個是阻塞等待,一個是非阻塞等待)。

主要代碼是這兩個, 其中numGains 是增益因子數組元素個數,因為數組是從零開始的,所以numGains個數減1就是表示數組最大索引數, 如果沒達到最大索引,即還未達最大增益,currentGainIndex++。

 if (upReading== LOW && currentGainIndex < numGains - 1) {  // 按下且未達最大增益currentGainIndex++;Serial.println("增益切換至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切換}

currentGainIndex > 0表示還未到最小索引,所以currentGainIndex- -

if (downReading == LOW && currentGainIndex > 0) {  // 按下且未達最小增益currentGainIndex--;Serial.println("增益切換至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切換}

按鍵是否按下是通過判斷upReading和downReading是否變為LOW實現。

為了方便控制音量,這里我還嘗試了使用旋轉編碼器來控制音量,主要代碼如下:

#include <ESP32Encoder.h>
// 編碼器引腳
#define MODE_DT_PIN  32  // A 相
#define MODE_CLK_PIN 35  // B 相#define MODE_STEP    2   // 每 2 個計數觸發ESP32Encoder modeEncoder;void KeyUp() {if (currentGainIndex < numGains - 1) {currentGainIndex++;Serial.println("增益切換至: " + String(gainFactors[currentGainIndex]));}
}void KeyDn() {if (currentGainIndex > 0) {currentGainIndex--;Serial.println("增益切換至: " + String(gainFactors[currentGainIndex]));}
}/*------EC11 控制函數------*/
void EC11_Control() {static int lastModeCount = 0;static unsigned long lastRotateTime = 0;int currentModeCount = modeEncoder.getCount();if (abs(currentModeCount - lastModeCount) >= MODE_STEP) {lastRotateTime = millis();if (currentModeCount > lastModeCount) {KeyUp(); // 順時針增加增益Serial.println("向下");} else {KeyDn(); // 逆時針減少增益Serial.println("向上");}modeEncoder.setCount(0); // 重置計數lastModeCount = 0;}// 長時間無操作時輸出停止if (millis() - lastRotateTime > 1000) {//Serial.println("停止旋轉");lastRotateTime = millis(); }
}

理解了按鍵控制增益因子的原理,這里也是一樣的。我們在EC11_Control()調用 KeyUp()和KeyDn()這兩個函數。采用編碼器的時候還需要注意在setup()函數里面添加初始化編碼器的相關代碼。

void setup() {
// 初始化編碼器pinMode(MODE_CLK_PIN, INPUT_PULLUP);pinMode(MODE_DT_PIN, INPUT_PULLUP);modeEncoder.attachHalfQuad(MODE_CLK_PIN, MODE_DT_PIN);//配置編碼器為半四分之一模式,只計數部分狀態變化(每步約 1-2 個計數)modeEncoder.setFilter(50); // 濾波值modeEncoder.setCount(0);}

現象

ESP32驅動inmp441采集音頻并實時播放

注意事項

  1. 音頻輸出可以用耳機去接收聽到聲音,也可以使用AUX線外接功放板。PCM5102A板子上也有L/R左右聲道接口,也可以杜邦線接到其他功放上(比如上一期的TDA2030A功放模塊)。
  2. INM441是全向麥克風模塊,把揚聲器和麥克風放在一起使用很容易引起嘯叫,有耳機的話基本不會有這個問題。
  3. 根據前面嘯叫問題,增益不宜調太大,一是容易引起嘯叫,二是靠近說話容易引起破音,增益大了對遠距離采集聲音較好。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/84341.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84341.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/84341.shtml

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

相關文章

馮諾依曼架構是什么?

馮諾依曼架構是什么&#xff1f; 馮諾依曼架構&#xff08;Von Neumann Architecture&#xff09;是現代計算機的基礎設計框架&#xff0c;由數學家約翰馮諾依曼&#xff08;John von Neumann&#xff09;及其團隊在1945年提出。其核心思想是通過統一存儲程序與數據&#xff0…

【持續更新】linux網絡編程試題

問題1 請簡要說明TCP/IP協議棧的四層結構&#xff0c;并分別舉出每一層出現的典型協議或應用。 答案 應用層&#xff1a;ping,telnet,dns 傳輸層&#xff1a;tcp,udp 網絡層&#xff1a;ip,icmp 數據鏈路層&#xff1a;arp,rarp 問題2 下列協議或應用分別屬于TCP/IP協議…

橢圓曲線密碼學(ECC)

一、ECC算法概述 橢圓曲線密碼學&#xff08;Elliptic Curve Cryptography&#xff09;是基于橢圓曲線數學理論的公鑰密碼系統&#xff0c;由Neal Koblitz和Victor Miller在1985年獨立提出。相比RSA&#xff0c;ECC在相同安全強度下密鑰更短&#xff08;256位ECC ≈ 3072位RSA…

【JVM】- 內存結構

引言 JVM&#xff1a;Java Virtual Machine 定義&#xff1a;Java虛擬機&#xff0c;Java二進制字節碼的運行環境好處&#xff1a; 一次編寫&#xff0c;到處運行自動內存管理&#xff0c;垃圾回收的功能數組下標越界檢查&#xff08;會拋異常&#xff0c;不會覆蓋到其他代碼…

React 基礎入門筆記

一、JSX語法規則 1. 定義虛擬DOM時&#xff0c;不要寫引號 2.標簽中混入JS表達式時要用 {} &#xff08;1&#xff09;.JS表達式與JS語句&#xff08;代碼&#xff09;的區別 &#xff08;2&#xff09;.使用案例 3.樣式的類名指定不要用class&#xff0c;要用className 4.內…

Linux鏈表操作全解析

Linux C語言鏈表深度解析與實戰技巧 一、鏈表基礎概念與內核鏈表優勢1.1 為什么使用鏈表&#xff1f;1.2 Linux 內核鏈表與用戶態鏈表的區別 二、內核鏈表結構與宏解析常用宏/函數 三、內核鏈表的優點四、用戶態鏈表示例五、雙向循環鏈表在內核中的實現優勢5.1 插入效率5.2 安全…

SQL進階之旅 Day 19:統計信息與優化器提示

【SQL進階之旅 Day 19】統計信息與優化器提示 文章簡述 在數據庫性能調優中&#xff0c;統計信息和優化器提示是兩個至關重要的工具。統計信息幫助數據庫優化器評估查詢成本并選擇最佳執行計劃&#xff0c;而優化器提示則允許開發人員對優化器的行為進行微調。本文深入探討了…

安寶特方案丨船舶智造AR+AI+作業標準化管理系統解決方案(維保)

船舶維保管理現狀&#xff1a;設備維保主要由維修人員負責&#xff0c;根據設備運行狀況和維護計劃進行定期保養和故障維修。維修人員憑借經驗判斷設備故障原因&#xff0c;制定維修方案。 一、痛點與需求 1 Arbigtec 人工經驗限制維修效率&#xff1a; 復雜設備故障的診斷和…

MFC內存泄露

1、泄露代碼示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 獲取 Ribbon Bar 指針// 創建自定義按鈕CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…

基于區塊鏈的供應鏈溯源系統:構建與實踐

前言 在當今全球化的經濟環境中&#xff0c;供應鏈的復雜性不斷增加&#xff0c;商品從原材料采購到最終交付給消費者的過程涉及多個環節和眾多參與者。如何確保供應鏈的透明度、可追溯性和安全性&#xff0c;成為企業和消費者關注的焦點。區塊鏈技術以其去中心化、不可篡改和透…

Web攻防-SQL注入數據格式參數類型JSONXML編碼加密符號閉合

知識點&#xff1a; 1、Web攻防-SQL注入-參數類型&參數格式 2、Web攻防-SQL注入-XML&JSON&BASE64等 3、Web攻防-SQL注入-數字字符搜索等符號繞過 案例說明&#xff1a; 在應用中&#xff0c;存在參數值為數字&#xff0c;字符時&#xff0c;符號的介入&#xff0c…

探秘鴻蒙 HarmonyOS NEXT:實戰用 CodeGenie 構建鴻蒙應用頁面

在開發鴻蒙應用時&#xff0c;你是否也曾為一個頁面的布局反復調整&#xff1f;是否還在為查 API、寫模板代碼而浪費大量時間&#xff1f;今天帶大家實戰體驗一下鴻蒙官方的 AI 編程助手——CodeGenie&#xff08;代碼精靈&#xff09; &#xff0c;如何從 0 到 1 快速構建一個…

DBAPI如何優雅的獲取單條數據

API如何優雅的獲取單條數據 案例一 對于查詢類API&#xff0c;查詢的是單條數據&#xff0c;比如根據主鍵ID查詢用戶信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默認返回的數據格式是多條的&#xff0c;如下&#xff1a; {&qu…

使用Whisper本地部署實現香港版粵語+英語混合語音轉文字方案

今天要一個非常好的朋友有個工作&#xff0c;就是要把醫院醫生診斷的說話記錄轉成文字&#xff0c;之前都是她本人一句一句的聽&#xff0c;然后記錄下來的&#xff0c;我想通過ai 來解決這個問題。 她的需求如下&#xff1a; 不能把數據傳到網上&#xff0c;隱私問題所以需要…

案例分享--汽車制動卡鉗DIC測量

制動系統是汽車的主要組成部分&#xff0c;是汽車的主要安全部件之一。隨著車輛性能的不斷提高&#xff0c;車速不斷提升&#xff0c;對車輛的制動系統也隨之提出了更高要求&#xff0c;因此了解車輛制動系統中每個部件的動態行為成為了制動系統優化的主要途徑&#xff0c;同時…

保姆級教程:在無網絡無顯卡的Windows電腦的vscode本地部署deepseek

文章目錄 1 前言2 部署流程2.1 準備工作2.2 Ollama2.2.1 使用有網絡的電腦下載Ollama2.2.2 安裝Ollama&#xff08;有網絡的電腦&#xff09;2.2.3 安裝Ollama&#xff08;無網絡的電腦&#xff09;2.2.4 安裝驗證2.2.5 修改大模型安裝位置2.2.6 下載Deepseek模型 2.3 將deepse…

【Redis技術進階之路】「原理分析系列開篇」分析客戶端和服務端網絡誦信交互實現(服務端執行命令請求的過程 - 初始化服務器)

服務端執行命令請求的過程 【專欄簡介】【技術大綱】【專欄目標】【目標人群】1. Redis愛好者與社區成員2. 后端開發和系統架構師3. 計算機專業的本科生及研究生 初始化服務器1. 初始化服務器狀態結構初始化RedisServer變量 2. 加載相關系統配置和用戶配置參數定制化配置參數案…

VB.net復制Ntag213卡寫入UID

本示例使用的發卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、讀取舊Ntag卡的UID和數據 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click輕松讀卡技術支持:網站:Dim i, j As IntegerDim cardidhex, …

SQL SERVER 數據庫遷移的三種方法!

要將SQL Server從研發環境的把數據庫結構(不含數據)遷移至生產環境,可通過以下幾種方法實現。以下是具體操作步驟及適用場景: ?? 一、使用SSMS圖形界面生成結構腳本(推薦新手) 通過SQL Server Management Studio的生成腳本向導,僅導出數據庫架構: ??連接測試庫??…

C# 快速檢測 PDF 是否加密,并驗證正確密碼

引言&#xff1a;為什么需要檢測PDF加密狀態&#xff1f; 在批量文檔處理系統&#xff08;如 OCR 文字識別、內容提取、格式轉換&#xff09;中&#xff0c;加密 PDF 無法直接操作。檢測加密狀態可提前篩選文件&#xff0c;避免流程因密碼驗證失敗而中斷。 本文使用 Free Spire…