8:從USB攝像頭把聲音拿出來--ALSA大佬登場!

前言

????????前面的章節我們從認識攝像頭開始,逐漸認識的YCbCr,并對其進行了H264的編碼以及MP4封裝。整個過程中,我們大致使用了V4L2和FFmpeg這兩個重量級工具,就像我們前面章節所講,V4L2只是給圖像做服務的,并不參與音頻。

????????在第3章中,我們說過V4L2這位大哥只管視頻,不管音頻。那音頻誰來管?ALSA大佬管!

????????那么這一章,我們重點討論一下ALSA,并且我們列一個目標:從USB攝像頭獲取音頻流,并且編碼成AAC!

一、ALSA介紹

1、ALSA是什么
  • ?全稱:?? ?Advanced Linux Sound Architecture? (高級 Linux 聲音架構)
  • ?本質:?? ?Linux 內核的音頻子系統和驅動框架。它提供了從底層硬件聲卡驅動到上層用戶空間應用程序接口(API)的一整套解決方案。
  • ?目的:?? 管理和驅動計算機的聲卡硬件,允許應用程序播放和錄制聲音。
  • ?歷史:?? 在 2.6 內核中正式取代了老舊的 ?OSS (Open Sound System)?,成為 Linux 默認的標準聲音系統。
2、ALSA 的核心組成部分和功能
  1. ?內核驅動:??

    • 這部分包含在內核源代碼中 (/sound?目錄)。
    • 直接與物理聲卡硬件(集成、獨立聲卡、USB 聲卡等)通信,處理中斷、DMA、硬件寄存器讀寫等底層操作。
    • 為每種支持的聲卡芯片或型號提供特定的驅動程序模塊,等我們有能力后,也可以為一個音頻芯片或驅動模塊編寫驅動程序,現在還是先用起來。
  2. ?用戶空間庫 (libasound.so?- ALSA library):??

    • 這是應用程序主要交互的接口。
    • 提供了一組豐富、統一的 API (稱為 ?ALSA API? 或 ?alsa-lib API),讓應用程序開發者無需關心底層硬件的細節即可播放或錄制音頻。
    • 庫負責將應用程序的請求(如“播放這個 PCM 數據流”)傳遞給內核驅動,并處理緩沖區、格式轉換、插件等高級功能。
    • 支持多種音頻格式(采樣率、位深、通道數)、參數設置(緩沖區大小、周期數)。
  3. ?設備文件 (/dev/snd/?目錄下):??

    • 內核驅動為用戶空間暴露的接口文件。雖然應用程序通常通過?libasound?訪問音頻功能,但理解這些設備文件有助于調試。
    • 主要設備:
      • /dev/snd/controlC#: 控制設備 (Control device),用于混音器控制(如?alsamixer?/?amixer?使用)。
      • /dev/snd/pcmC#D#: PCM 播放/錄制設備 (Playback/Capture device)。C#?表示聲卡號 (Card),D#?表示該聲卡上的設備號 (Device)。我們編程程序的時候,會用到這個。

二、ALSA初體驗

????????為了能夠在程序中使用ALSA,需要安裝ALSA開發庫:

sudo apt-get install libasound2-dev

????????下面直接給出通過ALSA獲取USB攝像頭的PCM音頻數據的代碼,并根據這份代碼進行講解:

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>#define SAMPLE_RATE     22050   // 采樣率
#define CHANNELS        1       // 單聲道
#define PERIOD_SIZE     512     // 周期大小
#define PERIODS        4        // 緩沖區周期數
#define RECORD_SECONDS 5        // 錄音時長(秒)int main() {int rc;snd_pcm_t *capture_handle;snd_pcm_hw_params_t *hw_params;FILE *pcm_file;short *buffer;int dir = 0;// 1. 打開音頻設備rc = snd_pcm_open(&capture_handle, "plughw:1,0", SND_PCM_STREAM_CAPTURE, 0);if (rc < 0) {fprintf(stderr, "無法打開音頻設備: %s\n", snd_strerror(rc));return 1;}// 2. 分配硬件參數結構snd_pcm_hw_params_alloca(&hw_params);// 3. 初始化硬件參數rc = snd_pcm_hw_params_any(capture_handle, hw_params);if (rc < 0) {fprintf(stderr, "無法初始化硬件參數: %s\n", snd_strerror(rc));goto cleanup;}// 4. 設置訪問類型(交錯模式)rc = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);if (rc < 0) {fprintf(stderr, "無法設置訪問類型: %s\n", snd_strerror(rc));goto cleanup;}// 5. 設置采樣格式(16位小端)rc = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE);if (rc < 0) {fprintf(stderr, "無法設置采樣格式: %s\n", snd_strerror(rc));goto cleanup;}// 6. 設置采樣率unsigned int sample_rate = SAMPLE_RATE;rc = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &sample_rate, &dir);if (rc < 0) {fprintf(stderr, "無法設置采樣率: %s\n", snd_strerror(rc));goto cleanup;}printf("實際采樣率: %u Hz\n", sample_rate);// 7. 設置聲道數(單聲道)rc = snd_pcm_hw_params_set_channels(capture_handle, hw_params, CHANNELS);if (rc < 0) {fprintf(stderr, "無法設置聲道數: %s\n", snd_strerror(rc));goto cleanup;}// 8. 設置周期大小snd_pcm_uframes_t period_size = PERIOD_SIZE;rc = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &period_size, &dir);if (rc < 0) {fprintf(stderr, "無法設置周期大小: %s\n", snd_strerror(rc));goto cleanup;}printf("實際周期大小: %lu 幀\n", period_size);// 9. 設置周期數(緩沖區大小 = 周期大小 * 周期數)unsigned int periods = PERIODS;rc = snd_pcm_hw_params_set_periods_near(capture_handle, hw_params, &periods, &dir);if (rc < 0) {fprintf(stderr, "無法設置周期數: %s\n", snd_strerror(rc));goto cleanup;}printf("實際周期數: %u\n", periods);// 10. 應用硬件參數rc = snd_pcm_hw_params(capture_handle, hw_params);if (rc < 0) {fprintf(stderr, "無法設置參數: %s\n", snd_strerror(rc));goto cleanup;}// 11. 準備音頻緩沖區buffer = malloc(period_size * sizeof(short));if (!buffer) {fprintf(stderr, "無法分配緩沖區\n");goto cleanup;}// 12. 打開輸出文件pcm_file = fopen("test.pcm", "wb");if (!pcm_file) {fprintf(stderr, "無法創建輸出文件\n");goto cleanup;}printf("開始錄音...\n");// 13. 錄音循環int frames = 0;const int total_frames = (sample_rate * RECORD_SECONDS) / period_size;while (frames < total_frames) {rc = snd_pcm_readi(capture_handle, buffer, period_size);if (rc == -EPIPE) {fprintf(stderr, "緩沖區溢出,正在恢復\n");snd_pcm_prepare(capture_handle);continue;} else if (rc < 0) {fprintf(stderr, "讀取錯誤: %s\n", snd_strerror(rc));break;} else if (rc != period_size) {fprintf(stderr, "短幀讀取,期望 %lu,實際 %d\n", period_size, rc);}// 寫入PCM數據到文件fwrite(buffer, sizeof(short), rc, pcm_file);frames++;printf("\r已錄制 %.1f 秒... ", (float)frames * period_size / sample_rate);fflush(stdout);}printf("\n錄音完成!保存為 test.pcm\n");cleanup:if (capture_handle) {snd_pcm_close(capture_handle);}if (buffer) {free(buffer);}if (pcm_file) {fclose(pcm_file);}return 0;
}

????????代碼整體上還是比較簡單的,邏輯也很清晰,只對新出現的部分做一些補充:

????????1、snd_pcm_xxx是ALSA庫(alsa-lib)接口,編譯的時候,需要鏈接 -lsound

????????2、snd_pcm_open的參數中,有一個“plughw:1,0”,這里的plug指的是插件,比如應用程序想要獲取44100采樣率的數據,但是硬件只支持22050,那么plug就可以自動將音頻數據從22050轉成44100給到應用程序,主要是考慮到兼容性問題。但是在嵌入式中,音頻硬件和驅動是固定的,不考慮兼容性,所以在打開音頻設備時,使用“hw:1,0”即可。畢竟兼容性是要犧牲算力資源和內存資源的。

????????3、“hw:1,0“的命名規則如下:

????????card如果是0,代表是系統默認聲卡。如果是1,一般是外接聲卡,比如USB聲卡。

????????設備號是從0開始的,我們的USB攝像頭設備號只有一個,其他的不清楚。

????????所以“hw:1,0”表示的是:硬件訪問方式,外置聲卡,且聲卡的設備號為0。

????????在Ubuntu中,在插入USB攝像頭之前,/dev/snd里面的設備如下:

by-path  controlC0  midiC0D0  pcmC0D0c  pcmC0D0p  pcmC0D1p  seq  timer

????????插入USB攝像頭后,/dev/snd里面的設備如下:

by-id  by-path  controlC0  controlC1  midiC0D0  pcmC0D0c  pcmC0D0p  pcmC0D1p  pcmC1D0c  seq  timer

????????可以看到多出了“controlC1”和“pcmC1D0c”,后者就是聲卡1,設備0。

????????4、周期是什么?

????????在代碼中,音頻的采樣率(每秒鐘采樣多少個點)和單聲道都好理解,但是PERIODS/PERIODS_SIZE是什么?

????????當ALSA從USB攝像頭獲取到音頻數據后,會循環放在P個buffer中,這個P就是就是周期數,也就是PERIODS。每個音頻幀的大小都是固定的,也就是PERRIODS_SIZE。其中音頻幀又是交錯格式(Interleaved)存儲的。如果是立體聲(左右雙聲道):L R L R... 我手里的攝像頭是單聲道的,就算是交錯模式,存儲方式也是單聲道的:L L L L ...

????????在snd_pcm_readi讀取音頻數據時,有一個錯誤EPIPE處理。

????????在播放音頻的時候,EPIPE代表的是欠載,表示應用程序填充數據太慢,硬件已經消耗完所有的周期,需要加快數據填充用于播放。

????????在錄音的時候,EPIPE代表的是超限,意思是應用程序snd_pcm_readi讀取的不及時,導致buffer中的數據溢出了,需要及時讀取。

????????5、編譯并運行

gcc uvc_voice_streaming.c -o uvc_voice_streaming -lasound -lavcodec -lavutil

????????運行后,就可以生成test.pcm,因為這個是pcm數據,沒有頭部結構,一般的播放器無法進行播放。筆者使用的是GoldWave,在打開的時候,參數選項要正確,否則無法正確播放錄音。

三、使用FFmpeg進行AAC編碼

????????FFmpeg在前面幾章介紹過,雖然一個是視頻,一個是音頻,但是處理方式都差不多,這里就不再重復。

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <time.h>#define SAMPLE_RATE     22050   // 采樣率
#define CHANNELS        1       // 單聲道
#define PERIOD_SIZE     512     // 周期大小
#define PERIODS        4        // 緩沖區周期數
#define RECORD_SECONDS 5        // 錄音時長(秒)// ADTS頭部長度為7個字節
static uint8_t *adts_header = NULL;// 生成ADTS頭
static void add_adts_header(uint8_t *header, int packet_size, int sample_rate_index, int channels) {// Sync Pointheader[0] = 0xFF;header[1] = 0xF1;// Profile(2), Sampling Freq(4), Private(1), Channel Config(1)header[2] = ((2 - 1) << 6)  // AAC-LC = 2| (sample_rate_index << 2)| ((channels & 4) >> 2);// Channel Config(2), Original(1), Home(1), Copyright ID(1), Copyright Start(1), Frame Length(2)header[3] = ((channels & 3) << 6)| ((packet_size + 7) >> 11);// Frame Length(8)header[4] = ((packet_size + 7) >> 3) & 0xFF;// Frame Length(3), Buffer Fullness(5)header[5] = (((packet_size + 7) & 0x07) << 5)| 0x1F;// Buffer Fullness(6), Raw Data Blocks(2)header[6] = 0xFC;
}// 獲取采樣率索引
static int get_sample_rate_index(int sample_rate) {int sample_rates[] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000};for (int i = 0; i < 12; i++) {if (sample_rate == sample_rates[i]) {return i;}}return 7; // 默認使用22050Hz的索引
}int main() {int rc;snd_pcm_t *capture_handle;snd_pcm_hw_params_t *hw_params;FILE *aac_file;short *buffer;int dir = 0;// FFmpeg變量AVCodec *codec = NULL;AVCodecContext *codec_ctx = NULL;AVFrame *frame = NULL;AVPacket *pkt = NULL;// 1. 打開音頻設備rc = snd_pcm_open(&capture_handle, "plughw:1,0", SND_PCM_STREAM_CAPTURE, 0);if (rc < 0) {fprintf(stderr, "無法打開音頻設備: %s\n", snd_strerror(rc));return 1;}// 2. 分配硬件參數結構snd_pcm_hw_params_alloca(&hw_params);// 3. 初始化硬件參數rc = snd_pcm_hw_params_any(capture_handle, hw_params);if (rc < 0) {fprintf(stderr, "無法初始化硬件參數: %s\n", snd_strerror(rc));goto cleanup;}// 4. 設置訪問類型(交錯模式)rc = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);if (rc < 0) {fprintf(stderr, "無法設置訪問類型: %s\n", snd_strerror(rc));goto cleanup;}// 5. 設置采樣格式(16位小端)rc = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE);if (rc < 0) {fprintf(stderr, "無法設置采樣格式: %s\n", snd_strerror(rc));goto cleanup;}// 6. 設置采樣率unsigned int sample_rate = SAMPLE_RATE;rc = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &sample_rate, &dir);if (rc < 0) {fprintf(stderr, "無法設置采樣率: %s\n", snd_strerror(rc));goto cleanup;}printf("實際采樣率: %u Hz\n", sample_rate);// 7. 設置聲道數(單聲道)rc = snd_pcm_hw_params_set_channels(capture_handle, hw_params, CHANNELS);if (rc < 0) {fprintf(stderr, "無法設置聲道數: %s\n", snd_strerror(rc));goto cleanup;}// 8. 設置周期大小snd_pcm_uframes_t period_size = PERIOD_SIZE;rc = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &period_size, &dir);if (rc < 0) {fprintf(stderr, "無法設置周期大小: %s\n", snd_strerror(rc));goto cleanup;}printf("實際周期大小: %lu 幀\n", period_size);// 9. 設置周期數(緩沖區大小 = 周期大小 * 周期數)unsigned int periods = PERIODS;rc = snd_pcm_hw_params_set_periods_near(capture_handle, hw_params, &periods, &dir);if (rc < 0) {fprintf(stderr, "無法設置周期數: %s\n", snd_strerror(rc));goto cleanup;}printf("實際周期數: %u\n", periods);// 10. 應用硬件參數rc = snd_pcm_hw_params(capture_handle, hw_params);if (rc < 0) {fprintf(stderr, "無法設置參數: %s\n", snd_strerror(rc));goto cleanup;}// 初始化FFmpeg編碼器codec = avcodec_find_encoder(AV_CODEC_ID_AAC);if (!codec) {fprintf(stderr, "找不到AAC編碼器\n");goto cleanup;}codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "無法分配編碼器上下文\n");goto cleanup;}// 設置AAC編碼器參數codec_ctx->bit_rate = 64000;  // 64 kbpscodec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;  // AAC需要浮點平面格式codec_ctx->sample_rate = SAMPLE_RATE;codec_ctx->channel_layout = AV_CH_LAYOUT_MONO;  // 單聲道codec_ctx->channels = CHANNELS;codec_ctx->profile = FF_PROFILE_AAC_LOW;  // AAC-LCrc = avcodec_open2(codec_ctx, codec, NULL);if (rc < 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(rc, errbuf, AV_ERROR_MAX_STRING_SIZE);fprintf(stderr, "無法打開編碼器: %s\n", errbuf);goto cleanup;}// 分配音頻幀frame = av_frame_alloc();if (!frame) {fprintf(stderr, "無法分配音頻幀\n");goto cleanup;}frame->nb_samples = codec_ctx->frame_size;frame->format = codec_ctx->sample_fmt;frame->channel_layout = codec_ctx->channel_layout;frame->sample_rate = codec_ctx->sample_rate;rc = av_frame_get_buffer(frame, 0);if (rc < 0) {fprintf(stderr, "無法分配幀緩沖區\n");goto cleanup;}// 分配數據包pkt = av_packet_alloc();if (!pkt) {fprintf(stderr, "無法分配數據包\n");goto cleanup;}// 分配ADTS頭部緩沖區adts_header = (uint8_t *)malloc(7);if (!adts_header) {fprintf(stderr, "無法分配ADTS頭部緩沖區\n");goto cleanup;}// 準備音頻緩沖區buffer = malloc(period_size * sizeof(short));if (!buffer) {fprintf(stderr, "無法分配緩沖區\n");goto cleanup;}// 打開輸出文件aac_file = fopen("test.aac", "wb");if (!aac_file) {fprintf(stderr, "無法創建輸出文件\n");goto cleanup;}printf("開始錄音...\n");// 錄音循環int frames = 0;const int total_frames = (SAMPLE_RATE * RECORD_SECONDS) / period_size;float *samples = (float *)frame->data[0];int samples_index = 0;while (frames < total_frames) {rc = snd_pcm_readi(capture_handle, buffer, period_size);if (rc == -EPIPE) {fprintf(stderr, "緩沖區溢出,正在恢復\n");snd_pcm_prepare(capture_handle);continue;} else if (rc < 0) {fprintf(stderr, "讀取錯誤: %s\n", snd_strerror(rc));break;}// 將PCM數據轉換為浮點格式并填充到framefor (int i = 0; i < rc; i++) {samples[samples_index++] = buffer[i] / 32768.0f;if (samples_index >= frame->nb_samples) {// 幀滿了,進行編碼rc = avcodec_send_frame(codec_ctx, frame);if (rc < 0) {fprintf(stderr, "發送幀失敗\n");goto cleanup;}while (rc >= 0) {rc = avcodec_receive_packet(codec_ctx, pkt);if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF) {break;} else if (rc < 0) {fprintf(stderr, "接收包失敗\n");goto cleanup;}// 添加ADTS頭add_adts_header(adts_header, pkt->size, get_sample_rate_index(SAMPLE_RATE), CHANNELS);// 寫入ADTS頭和AAC數據fwrite(adts_header, 1, 7, aac_file);fwrite(pkt->data, 1, pkt->size, aac_file);av_packet_unref(pkt);}samples_index = 0;}}frames++;printf("\r已錄制 %.1f 秒... ", (float)frames * period_size / SAMPLE_RATE);fflush(stdout);}// 刷新編碼器avcodec_send_frame(codec_ctx, NULL);while (1) {rc = avcodec_receive_packet(codec_ctx, pkt);if (rc == AVERROR_EOF) {break;} else if (rc < 0) {fprintf(stderr, "刷新編碼器失敗\n");break;}// 添加ADTS頭并寫入最后的數據add_adts_header(adts_header, pkt->size, get_sample_rate_index(SAMPLE_RATE), CHANNELS);fwrite(adts_header, 1, 7, aac_file);fwrite(pkt->data, 1, pkt->size, aac_file);av_packet_unref(pkt);}printf("\n錄音完成!保存為 test.aac\n");cleanup:if (capture_handle) {snd_pcm_close(capture_handle);}if (buffer) {free(buffer);}if (aac_file) {fclose(aac_file);}if (codec_ctx) {avcodec_free_context(&codec_ctx);}if (frame) {av_frame_free(&frame);}if (pkt) {av_packet_free(&pkt);}if (adts_header) {free(adts_header);}return 0;
}

????????代碼的邏輯關系如下:

????????該代碼是在上一節代碼基礎上修改的。snd_pcm_readi之后,使用FFmpeg處理。這里只講新的知識點。

????????1、重采樣:

????????USB攝像頭傳過來的音頻數據是交錯模式(interleaved),即立體聲的時候排列方式是:L R L R...

????????但是FFmpeg要求的是平面格式(Plannar),即每個聲道單獨存放:LLLL...RRR...

????????因為我們只有單通道,所以存儲格式不需要更改,后面我們見到重采樣就知道怎么回事的。代碼里面只是對音頻數據進行了浮點重采樣,因為FFmpeg是要求浮點的。

????????2、AAC

????????AAC(Advanced Audio Coding)是現代音頻壓縮技術的巔峰之作,代表了心理聲學模型應用的最高水平。作為MPEG-4標準的核心音頻技術,AAC在效率、質量和靈活性方面都超越了前代MP3標準。筆者并沒有對AAC做過多研究,有興趣的道友可以稍微深入一下。

AAC常見容器格式

格式特點使用場景
?.aac原始ADTS流簡單存儲
?.m4a?MP4容器iTunes標準
.mp4?視頻容器視頻伴音
.3gp?移動設備手機錄制
?.ts?傳輸流數字電視

????????3、ADTS頭結構

// ADTS頭示例
uint8_t adts[7] = {0xFF, // Sync byte 10xF1, // Sync byte 2 + 保護位0x50, // 配置信息 (AAC-LC, 44.1kHz)0x80, // 聲道配置 + 幀長度高位0x1F, // 幀長度中位0xFC, // 幀長度低位 + 緩沖區0x70  // 幀計數器
};

????????有了ADTS頭,現代一般的播放器就能識別。

????????4、編譯和運行

gcc -o uvc_voice_streaming uvc_voice_streaming_aac+adts.c -lasound -lavcodec -lavutil -lm

????????運行后,可以得到test.aac文件,使用VLC或者其他播放器一般是可以播放的。

四、總結

????????本章節主要討論了ALSA框架下的音頻獲取的過程,可以看到應用程序還是比較簡單的,不需要對底層有過多的關注。

????????并使用FFmpeg對音頻數據進行了AAC編碼,有了之前的章節,我們對FFmpeg使用基本上已經得心應手了。

????????下一章將面臨關鍵的挑戰:如何精確同步來自V4L2的視頻幀和來自ALSA的音頻包的時間戳,并使用FFmpeg將它們無縫地封裝進MP4文件,實現真正的音視頻錄制。

????????再之后我們就要進入真正運動相機硬件方面的探討了。

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

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

相關文章

Linux 命令:useradd

Linux useradd 命令詳細教程 useradd 是 Linux 系統中用于創建新用戶賬戶的基礎命令&#xff0c;它通過配置文件&#xff08;如 /etc/passwd、/etc/shadow&#xff09;和默認設置自動完成用戶創建流程。本文將詳細介紹其用法、參數及相關配置。資料已經分類整理好&#xff1a;h…

Pytest之收集用例規則與運行指定用例

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 小伙伴們大家好呀&#xff0c;今天筆者會給大家講解一下pytest是如何收集我們寫好的用例&#xff1f;我們又有哪些方式來運行單個用例或者批量運行用例呢&#xff…

qt 使用memcpy進行內存拷貝時注意的問題

int offset sizeof(st_target_data);// 預先分配足夠空間this->featureData.resize(offsetsize);// 再執行拷貝memcpy(this->featureData.data()offset, dataa, size);注意 一定要在mencpy之前 使用resize分配足夠的空間&#xff0c;否則在方法退出時候會閃退&#xff…

微調性能趕不上提示工程怎么辦?Can Gradient Descent Simulate Prompting?——論文閱讀筆記

今天速讀一篇文章 Can Gradient Descent Simulate Prompting? 一句話總結 針對【新知識應用的場景里&#xff0c;FT效果往往追不上ICL】這個情況&#xff0c;作者引入MAML的思想↓ 內圈讓模型學習新知識形成知識FT模型&#xff1b; 外圈通過最小化ICL和知識FT模型的KL散度&…

從“直覺搶答”到“深度思考”:大模型的“慢思考”革命,思維鏈、樹、圖如何讓AI越來越像人?

注&#xff1a;此文章內容均節選自充電了么創始人&#xff0c;CEO兼CTO陳敬雷老師的新書《GPT多模態大模型與AI Agent智能體》&#xff08;跟我一起學人工智能&#xff09;【陳敬雷編著】【清華大學出版社】 GPT多模態大模型與AI Agent智能體書籍本章配套視頻課程【陳敬雷】 文…

Android系統的問題分析筆記 - Android上的調試方式 debuggerd

debuggerd 是 Android 系統中的一個重要調試工具&#xff0c;主要用于生成進程崩潰時的核心轉儲&#xff08;core dump&#xff09;和調試信息&#xff08;如堆棧跟蹤&#xff09;。以下是關于 debuggerd 的詳細說明&#xff1a; 1. 基本功能 崩潰分析&#xff1a;當 Native 進…

python 雙下劃線開頭函數

在 Python 里&#xff0c;雙下劃線開頭的函數&#xff08;準確地說是方法&#xff09;有著特殊的用途和意義。下面為你詳細介紹相關內容&#xff1a; 1. 類的特殊方法&#xff08;魔術方法&#xff09; 以雙下劃線開頭和結尾的方法&#xff0c;被稱為特殊方法或者魔術方法&…

VyOS起步指南:用Docker快速搭建網絡實驗環境

文章目錄1. VyOS是什么&#xff1f;為什么選擇它&#xff1f;2. 五分鐘快速部署&#xff1a;Docker方案3. 進入容器&#xff1a;初探VyOS世界4. 核心操作&#xff1a;像開發者一樣思考5. 踩坑提醒&#xff1a;新手常見問題6. 結語&#xff1a;網絡即代碼的未來1. VyOS是什么&am…

動態規劃理論基礎,LeetCode 509. 斐波那契數 LeetCode 70. 爬樓梯 LeetCode 746. 使用最小花費爬樓梯

動態規劃理論基礎動態規劃&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;簡稱DP&#xff0c;如果某一問題有很多重疊子問題&#xff0c;使用動態規劃是最有效的。所以動態規劃中每一個狀態一定是由上一個狀態推導出來的&#xff0c;這一點就區分于貪心&#xff…

暑期自學嵌入式——Day02(C語言階段)

點關注不迷路喲。你的點贊、收藏&#xff0c;一鍵三連&#xff0c;是我持續更新的動力喲&#xff01;&#xff01;&#xff01; 主頁&#xff1a; 一位搞嵌入式的 genius-CSDN博客https://blog.csdn.net/m0_73589512?spm1000.2115.3001.5343 目錄 Day02→數據類型&#xf…

如何單獨安裝設置包域名

前言 在 npm 中&#xff0c;直接通過 package-lock.json 無法單獨設置包的安裝地址&#xff0c;因為該文件是自動生成的依賴關系鎖定文件。但你可以通過以下方法間接實現&#xff1a; 一、在 package.json 中指定包來源&#xff08;推薦&#xff09; 在 package.json 的 depend…

存儲過程探秘:數據庫編程的藝術

文章目錄存儲過程語法格式BEGIN...END語句塊DECLARE&#xff08;聲明局部變量&#xff09;流控制語句if函數批處理操作測試2測試3存儲過程與函數的關系存儲過程 MYSQL的存儲過程是一組預處理的SQL語句&#xff0c;可以像函數一樣在數據庫中進行存儲和調用。 它們允許在數據庫…

非阻塞寫入核心:asyncio.StreamWriter 的流量控制與數據推送之道

在 asyncio 的異步編程框架中&#xff0c;如果說 asyncio.StreamReader 是你異步應用的數據輸入管道&#xff0c;那么 asyncio.StreamWriter 就是你異步應用的數據輸出管道。它是一個至關重要的組件&#xff0c;讓你能夠方便、高效且非阻塞地向連接的另一端&#xff08;如 TCP …

控制臺打開mysql服務報錯解決辦法

控制臺打開mysql服務報錯解決辦法這個MySQL錯誤表示訪問被拒絕&#xff0c;通常是因為沒有提供正確的用戶名和密碼。以下是幾種解決方法&#xff1a; 方法1&#xff1a;指定用戶名和密碼連接 mysql -u root -p然后輸入root用戶的密碼。 方法2&#xff1a;如果忘記了root密碼&am…

Unsloth 實戰:DeepSeek-R1 模型高效微調指南(下篇)

食用指南 本系列因篇幅原因拆分為上下兩篇&#xff1a; 上篇以基礎環境搭建為主&#xff0c;介紹了 Unsloth 框架、基座模型下載、導入基座模型、數據集下載/加載/清洗、SwanLab 平臺賬號注冊。 下篇&#xff08;本文&#xff09;以實戰微調為主&#xff0c;介紹預訓練、全量…

Ubuntu安裝Jenkins

Ubuntu安裝Jenkins方法1&#xff1a;使用官方的Jenkins倉庫1. 添加Jenkins倉庫2. 更新軟件包列表3. 安裝Jenkins4. 啟動Jenkins服務5. 設置Jenkins開機啟動6. 查找初始管理員密碼7. 訪問Jenkins方法2&#xff1a;使用Snap包&#xff08;適用于較新的Ubuntu版本&#xff09;1. 安…

ubuntu22.04下配置qt5.15.17開發環境

自從qt5.15版本開始&#xff0c;不再提供免費的離線安裝包&#xff0c;只能通過源碼自行編譯。剛好最近需要在ubuntu22.04下配置qt開發環境&#xff0c;于是寫篇文章記錄配置的過程。 其實一開始是想配置qt5.15.2的&#xff0c;但是在編譯配置參數這一步驟中出現如下報錯 em…

S7-1200 與 S7-300 CPS7-400 CP UDP 通信 Step7 項目編程

S7-1200 CPU 與S7-300 CP STEP7 UDP通信S7-1200 與 S7-300 CP 之間的以太網通信可以通過 UDP 協議來實現&#xff0c;使用的通信指令是在S7-1200 CPU 側調用通信-開放式用戶通信TSEND_C&#xff0c;TRCV_C指令或TCON&#xff0c;TDISCON&#xff0c;TUSEND&#xff0c;TURCV 指…

基于YOLOv11的無人機目標檢測實戰(Windows環境)

1. 環境搭建 1.1 硬件與操作系統 操作系統&#xff1a;Windows 11 CPU&#xff1a;Intel i7-9700 GPU&#xff1a;NVIDIA RTX 2080&#xff08;8GB顯存&#xff09; 1.2 安裝CUDA和cuDNN 由于YOLOv11依賴PyTorch的GPU加速&#xff0c;需要安裝CUDA和cuDNN&#xff1a; 安…

Spring Cloud分布式配置中心:架構設計與技術實踐

從單體到微服務&#xff1a;Spring Cloud 開篇與微服務設計 Spring Cloud服務注冊與發現&#xff1a;架構設計與技術實踐深度分析 在以往分享中&#xff0c;碼友們已經掌握了微服務的設計和注冊中心的設計&#xff0c;部分聰明的碼友已經察覺了&#xff0c;已經到了需要設計一個…