STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器
只用STM32单片机+SD卡+耳机插座,实现播放MP3播放器!
看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labmad占用的RAM更少。但是大多数参考的方案还是用了外接IIS接口WM98xx之类的音频DAC芯片播放音频,稍显复杂繁琐。STM32F407Vx本身就自带了2路12位DAC输出,最高刷新速度333kHz,除了分辨率差点意思,速度上对于MP3通常44.1kHz采样率来说,用来播放音频绰绰有余了。本文给的方案和源码,直接用STM32软解码MP3并使用自带的2个DAC输出引脚输出音频左右声道。
原理:STM32从SD读取MP3文件原始数据,发送给Helix库解码,Helix解码后输出PCM数据流,将此数据进一步处理转换后,按照左右声道分别存入DAC输出1和2缓存,通过定时器以MP3文件的采样率的频率提供DAC触发节拍,通过DMA取缓存中高12位数据给DAC,在DAC1和2引脚产生音频波形,通过电容耦合到耳机的左右声道上。
MP3源文件是一种经过若干算法,将原始音频数据压缩得来的,软件解码的过程是逆过程,将压缩的音频反向转换为记录了左右声道、幅值的数据流,通常是PCM格式。
PCM:是模拟信号以固定的采样频率转换成数字信号后的表现形式。记录了音频采样的数据,双通道、16bit的PCM数据格式是以0轴为中心,范围为-32768~32767的数值,每个数据占用2字节,左声道和右声道交替存储,如图。

软解码得到的PCM数据到STM32的DAC缓存需要进一步处理。STM32的DAC是12位的,其输入范围0~4095,而双通道16位的PCM音频数据是左右声道交替存储,且数据范围-32768~32767,因此PCM到STM32的DAC缓存要按照顺序一拆为二,分为左右声道,每个数据再加上32768,使其由short int的范围转换为unsigned short int,即0~65535。由于PCM数据是对音频的采样,因此调节音量(幅值)可以在此步骤一并处理,即音频数据 x 音量 /最大音量。至于DAC是12位,只需将DAC模式设置为左对齐12位,舍弃低4位即可。
到此,STM32的DAC输出引脚上应该已经有音频信号了,通常DAC引脚上串联一个1~10uF的电容用来耦合音频信号,电容越大音质越好,电容另一端接耳机插座的左声道/右声道,插上耳机就可以欣赏音乐啦!音质嘛,反正我是听不出来好不好,跟商品MP3播放器差不多。如果不串联电容,DAC引脚直连耳机插座左右声道也能听到声音,就是有些数字信号噪声也会传进来。如果希望噪声小一些,DAC引脚输出端加一个下图的低通滤波电路也是可以的。


Helix移植:
Helix源码的官网我没找到,直接用了野火的例程里面的代码,移植也很简单,不用改任何代码,只需要将Helix文件夹拷贝到工程目录里,然后在Keil中添加好文件,以及添加头文件途径,编译即可。工程目录如图。

源码:dac配置
dac.c
/********************************************************************************* @file dac.c* @author ZL* @version V0.0.1* @date September-20-2019* @brief DAC configuration.******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "dac.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define CNT_FREQ 84000000 // TIM6 counter clock (prescaled APB1)/* DHR registers offsets */
#define DHR12R1_OFFSET ((uint32_t)0x00000008)
#define DHR12R2_OFFSET ((uint32_t)0x00000014)
#define DHR12RD_OFFSET ((uint32_t)0x00000020)/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint32_t DAC_DHR12R1_ADDR = (uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_L;
uint32_t DAC_DHR12R2_ADDR = (uint32_t)DAC_BASE + DHR12R2_OFFSET + DAC_Align_12b_L;uint16_t DAC_buff[2][DAC_BUF_LEN]; //DAC1、DAC2输出缓冲/* Private function prototypes -----------------------------------------------*/
static void TIM6_Config(void);/* Private functions ---------------------------------------------------------*/
/*** @brief DAC初始化* @param none* @retval none
*/
void DAC_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;DAC_InitTypeDef DAC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;DAC_Init(DAC_Channel_1, &DAC_InitStructure);DAC_Init(DAC_Channel_2, &DAC_InitStructure);//配置DMADMA_InitTypeDef DMA_InitStruct;DMA_StructInit(&DMA_InitStruct);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R1_ADDR;DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[0];//DAC1DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;DMA_InitStruct.DMA_BufferSize = DAC_BUF_LEN;DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;DMA_InitStruct.DMA_Priority = DMA_Priority_High;DMA_InitStruct.DMA_Channel = DMA_Channel_7;DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA1_Stream5, &DMA_InitStruct);DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R2_ADDR;DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[1];//DAC2DMA_Init(DMA1_Stream6, &DMA_InitStruct);//开启DMA传输完成中断NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream6_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);DMA_ITConfig(DMA1_Stream6, DMA_IT_HT, ENABLE);// DMA_Cmd(DMA1_Stream5, ENABLE);
// DMA_Cmd(DMA1_Stream6, ENABLE);DAC_Cmd(DAC_Channel_1, ENABLE);DAC_Cmd(DAC_Channel_2, ENABLE);DAC_DMACmd(DAC_Channel_1, ENABLE);DAC_DMACmd(DAC_Channel_2, ENABLE);TIM6_Config();
}//配置DAC采样率和DMA数据长度,并启动DMA DAC
void DAC_DMA_Start(uint32_t freq, uint16_t len)
{//设置DMA缓冲长度需要停止DMADAC_DMA_Stop();//设置DMA DAC缓冲长度DMA_SetCurrDataCounter(DMA1_Stream5, len);DMA_SetCurrDataCounter(DMA1_Stream6, len);//设置定时器TIM_SetAutoreload(TIM6, (uint16_t)((CNT_FREQ)/freq));//启动DMA_Cmd(DMA1_Stream5, ENABLE);DMA_Cmd(DMA1_Stream6, ENABLE);
}//停止DMA DAC
void DAC_DMA_Stop(void)
{DMA_Cmd(DMA1_Stream5, DISABLE);DMA_Cmd(DMA1_Stream6, DISABLE);
}//定时器6用于设置DAC刷新率
static void TIM6_Config(void)
{TIM_TimeBaseInitTypeDef TIM6_TimeBase;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);TIM_TimeBaseStructInit(&TIM6_TimeBase); TIM6_TimeBase.TIM_Period = (uint16_t)((CNT_FREQ)/44100);TIM6_TimeBase.TIM_Prescaler = 0;TIM6_TimeBase.TIM_ClockDivision = 0;TIM6_TimeBase.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM6, &TIM6_TimeBase);TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);TIM_Cmd(TIM6, ENABLE);
}/*** @brief DAC out1 PA4输出电压* @param dat:dac数值:,0~4095* @retval none
*/
void DAC_Out1(uint16_t dat)
{DAC_SetChannel1Data(DAC_Align_12b_R, dat);DAC_SoftwareTriggerCmd(DAC_Channel_1, ENABLE);
}/*** @brief DAC out2 PA5输出电压* @param dat:dac数值:,0~4095* @retval none
*/
void DAC_Out2(uint16_t dat)
{DAC_SetChannel2Data(DAC_Align_12b_R, dat);DAC_SoftwareTriggerCmd(DAC_Channel_2, ENABLE);
}/********************************************* *****END OF FILE****/
源码:MP3播放流程 (原创野火,参考了野火的例程,本人进行整理和修改)
MP3player.c
/*
******************************************************************************
* @file mp3Player.c
* @author fire
* @version V1.0
* @date 2023-08-13
* @brief mp3解码
******************************************************************************
*/
#include <stdio.h>
#include <string.h>
#include "ff.h"
#include "mp3Player.h"
#include "mp3dec.h"
#include "dac.h"
#include "led.h"/* 推荐使用以下格式mp3文件:* 采样率:44100Hz* 声 道:2* 比特率:320kbps*//* 处理立体声音频数据时,输出缓冲区需要的最大大小为2304*16/8字节(16为PCM数据为16位),* 这里我们定义MP3BUFFER_SIZE为2304*/
#define MP3BUFFER_SIZE 2304
#define INPUTBUF_SIZE 3000static HMP3Decoder Mp3Decoder; /* mp3解码器指针 */
static MP3FrameInfo Mp3FrameInfo; /* mP3帧信息 */
static MP3_TYPE mp3player; /* mp3播放设备 */
volatile uint8_t Isread = 0; /* DMA传输完成标志 */
volatile uint8_t dac_ht = 0; //DAC dma 半传输标志uint32_t led_delay = 0;uint8_t inputbuf[INPUTBUF_SIZE]={0}; /* 解码输入缓冲区,1940字节为最大MP3帧大小 */
static short outbuffer[MP3BUFFER_SIZE]; /* 解码输出缓冲区*/static FIL file; /* file objects */
static UINT bw; /* File R/W count */
FRESULT result; //从SD卡读取MP3源文件进行解码,并传入DAC缓冲区
int MP3DataDecoder(uint8_t **read_ptr, int *bytes_left)
{int err = 0, i = 0, outputSamps = 0;//bufflag开始解码 参数:mp3解码结构体、输入流指针、输入流大小、输出流指针、数据格式err = MP3Decode(Mp3Decoder, read_ptr, bytes_left, outbuffer, 0);if (err != ERR_MP3_NONE) //错误处理{switch (err){case ERR_MP3_INDATA_UNDERFLOW:printf("ERR_MP3_INDATA_UNDERFLOW\r\n");result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);*read_ptr = inputbuf;*bytes_left = bw;break; case ERR_MP3_MAINDATA_UNDERFLOW:/* do nothing - next call to decode will provide more mainData */printf("ERR_MP3_MAINDATA_UNDERFLOW\r\n");break; default:printf("UNKNOWN ERROR:%d\r\n", err); // 跳过此帧if (*bytes_left > 0){(*bytes_left) --;read_ptr ++;}break;}return 0;}else //解码无错误,准备把数据输出到PCM{MP3GetLastFrameInfo(Mp3Decoder, &Mp3FrameInfo); //获取解码信息 /* 输出到DAC */outputSamps = Mp3FrameInfo.outputSamps; //PCM数据个数if (outputSamps > 0){if (Mp3FrameInfo.nChans == 1) //单声道{//单声道数据需要复制一份到另一个声道for (i = outputSamps - 1; i >= 0; i--){outbuffer[i * 2] = outbuffer[i];outbuffer[i * 2 + 1] = outbuffer[i];}outputSamps *= 2;}//if (Mp3FrameInfo.nChans == 1) //单声道}//if (outputSamps > 0)//将数据传送至DMA DAC缓冲区for (i = 0; i < outputSamps/2; i++){if(dac_ht == 1){DAC_buff[0][i] = outbuffer[2*i] * mp3player.ucVolume /100 + 32768;DAC_buff[1][i] = outbuffer[2*i+1] * mp3player.ucVolume /100 + 32768;}else{DAC_buff[0][i+outputSamps/2] = outbuffer[2*i] * mp3player.ucVolume /100 + 32768;DAC_buff[1][i+outputSamps/2] = outbuffer[2*i+1] * mp3player.ucVolume /100 + 32768;}}return 1;}//else 解码正常
}//读取一段MP3数据,并把读取的指针赋值read_ptr,长度赋值bytes_left
uint8_t read_file(const char *mp3file, uint8_t **read_ptr, int *bytes_left)
{result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);if(result != FR_OK){printf("读取%s失败 -> %d\r\n", mp3file, result);return 0;}else{*read_ptr = inputbuf;*bytes_left = bw;return 1;}
}/*** @brief MP3格式音频播放主程序* @param mp3file MP3文件路径* @retval 无*/
void mp3PlayerDemo(const char *mp3file)
{uint8_t *read_ptr = inputbuf;int read_offset = 0; /* 读偏移指针 */int bytes_left = 0; /* 剩余字节数 */ mp3player.ucStatus = STA_IDLE;mp3player.ucVolume = 15; //音量值,100满//尝试打开MP3文件result = f_open(&file, mp3file, FA_READ);if(result != FR_OK){printf("Open mp3file :%s fail!!!->%d\r\n", mp3file, result);result = f_close (&file);return; /* 停止播放 */}printf("当前播放文件 -> %s\n", mp3file);//初始化MP3解码器Mp3Decoder = MP3InitDecoder(); if(Mp3Decoder == 0){printf("初始化helix解码库设备失败!\r\n");return; /* 停止播放 */}else{printf("初始化helix解码库完成\r\n");}//尝试读取一段MP3数据,并把读取的指针赋值read_ptr,长度赋值bytes_leftif(!read_file(mp3file, &read_ptr, &bytes_left)){MP3FreeDecoder(Mp3Decoder);return; /* 停止播放 */}//尝试解码成功if(MP3DataDecoder(&read_ptr, &bytes_left)){//打印MP3信息printf(" \r\n Bitrate %dKbps", Mp3FrameInfo.bitrate/1000);printf(" \r\n Samprate %dHz", Mp3FrameInfo.samprate);printf(" \r\n BitsPerSample %db", Mp3FrameInfo.bitsPerSample);printf(" \r\n nChans %d", Mp3FrameInfo.nChans);printf(" \r\n Layer %d", Mp3FrameInfo.layer);printf(" \r\n Version %d", Mp3FrameInfo.version);printf(" \r\n OutputSamps %d", Mp3FrameInfo.outputSamps);printf("\r\n");//启动DAC,开始发声if (Mp3FrameInfo.nChans == 1) //单声道要将outputSamps*2{DAC_DMA_Start(Mp3FrameInfo.samprate, 2 * Mp3FrameInfo.outputSamps);}else//双声道直接用Mp3FrameInfo.outputSamps{DAC_DMA_Start(Mp3FrameInfo.samprate, Mp3FrameInfo.outputSamps);}}else //解码失败{MP3FreeDecoder(Mp3Decoder);return;}/* 放音状态 */mp3player.ucStatus = STA_PLAYING;/* 进入主程序循环体 */while(mp3player.ucStatus == STA_PLAYING){//寻找帧同步,返回第一个同步字的位置read_offset = MP3FindSyncWord(read_ptr, bytes_left);if(read_offset < 0) //没有找到同步字{if(!read_file(mp3file, &read_ptr, &bytes_left))//重新读取一次文件再找{continue;//回到while(mp3player.ucStatus == STA_PLAYING)后面}}else//找到同步字{ read_ptr += read_offset; //偏移至同步字的位置bytes_left -= read_offset; //同步字之后的数据大小 if(bytes_left < 1024) //如果剩余的数据小于1024字节,补充数据{/* 注意这个地方因为采用的是DMA读取,所以一定要4字节对齐 */u16 i = (uint32_t)(bytes_left)&3; //判断多余的字节if(i) i=4-i; //需要补充的字节memcpy(inputbuf+i, read_ptr, bytes_left); //从对齐位置开始复制read_ptr = inputbuf+i; //指向数据对齐位置result = f_read(&file, inputbuf+bytes_left+i, INPUTBUF_SIZE-bytes_left-i, &bw);//补充数据if(result != FR_OK){printf("读取%s失败 -> %d\r\n",mp3file,result);break;}bytes_left += bw; //有效数据流大小}}//MP3数据解码并送入DAC缓存if(!MP3DataDecoder(&read_ptr, &bytes_left)){//如果播放出错,Isread置1,避免卡住死循环Isread = 1;}//mp3文件读取完成,退出if(file.fptr == file.fsize){printf("单曲播放完毕\r\n");break;} //等待DAC发送一半或全部中断while(Isread == 0){led_delay++;if(led_delay == 0xffffff){led_delay=0;LED1_TROG;}//Input_scan(); //等待DMA传输完成,此间可以运行按键扫描及处理事件}Isread = 0;}//运行到此处,说明单曲播放完成,收尾工作DAC_DMA_Stop();//停止喂DAC数据 mp3player.ucStatus = STA_IDLE;MP3FreeDecoder(Mp3Decoder);//清理缓存f_close(&file);
}void DMA1_Stream6_IRQHandler(void)
{if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_HTIF6) != RESET) //半传输{ dac_ht = 1; Isread=1;DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);}if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) != RESET) //全传输{dac_ht = 0;Isread=1;DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);}
}/***************************** (END OF FILE) *********************************/
源码:main.c
/********************************************************************************* @file ../User/main.c * @author ZL* @version V1.0* @date 2015-12-26* @brief Main program body******************************************************************************
**//* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "hw_includes.h"
#include "ff.h"
#include "exfuns.h"
#include "mp3Player.h"//遍历目录文件并打印输出
u8 scan_files(u8 * path)
{FRESULT res;char buf[512] = {0}; char *fn;#if _USE_LFNfileinfo.lfsize = _MAX_LFN * 2 + 1;fileinfo.lfname = buf;
#endifres = f_opendir(&dir,(const TCHAR*)path);if (res == FR_OK) { printf("\r\n"); while(1){res = f_readdir(&dir, &fileinfo); if (res != FR_OK || fileinfo.fname[0] == 0) break; #if _USE_LFNfn = *fileinfo.lfname ? fileinfo.lfname : fileinfo.fname;
#else fn = fileinfo.fname;
#endif printf("%s/", path); printf("%s\r\n", fn); } } return res;
}/*** @brief Main program* @param None* @retval None*/
int main(void)
{ delay_init(168);usart1_Init(115200);LED_Init();DAC_Config();if(!SD_Init()){exfuns_init(); //为fatfs相关变量申请内存 f_mount(fs[0],"0:",1); //挂载SD卡 }//打印SD目录和文件scan_files("0:");LED0_ON;while (1){mp3PlayerDemo("0:/断桥残雪.MP3");mp3PlayerDemo("0:/张国荣-玻璃之情.MP3");delay_ms(50);}
}
为方便调试测试,使用usart1打印数据。实测效果:


程序源码与原理图,测试音频:
链接:https://pan.baidu.com/s/10hYXkrqnuBQgs0DWKLUUOA?pwd=iatt
提取码:iatt
知道这里下载要积分登录什么的麻烦得很,所以程序放到百度网盘了,假如连接失效,记得在评论区喊我更新!
理论上STM32F1或者其他系列也能用这个方案,要自己改改测试喽,本文把思路分享出来抛砖引玉。
相关文章:
STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器
只用STM32单片机SD卡耳机插座,实现播放MP3播放器! 看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labm…...
STM32 CAN 过滤器设置
做个笔记吧 ,免得以后忘记了 芯片是stm32F207 ,用cubeMX 6.80 版本生成 CAN 的使用总体包含4个部分 第一步:CAN初始化,配置波特率 (cubeMX 里面配置好后自动生成,不需要手动添加) MX_CAN1_Init(); 第二步&#…...
日常BUG—— maven编译报错
😜作 者:是江迪呀✒️本文关键词:日常BUG、BUG、问题分析☀️每日 一言 :存在错误说明你在进步! 一、问题描述 一个maven项目在由于在代码中书写了如下代码: public static ConcurrentMap<…...
Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理
Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 目录 Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 一、简单介绍 二、实现原理 三、实现步骤 四、关键代码 一、简单介绍 Unity 工具类,自己整理的一些游戏开发可…...
学习Vue:插值表达式和指令
在 Vue.js 中,Vue 实例与数据绑定是构建动态交互界面的关键。在这篇文章中,我们将重点介绍 Vue 实例中两种实现数据绑定的方式:插值表达式和指令。这些机制允许您将数据无缝地渲染到界面上,实现实时的数据更新和展示。 插值表达式…...
echart 3d立体颜色渐变柱状图
如果可以实现记得点赞分享,谢谢老铁~ 1.需求描述 根据业务需求将不同的法律法规,展示不同的3d立体渐变柱状图。 2.先看下效果图 3. 确定三面的颜色,这里我是自定义的颜色 // 右面生成颜色const rightColorArr ref(["#79D…...
linux shell变量
linux shell变量 1、变量命名规则2、只读变量3、删除变量 1、变量命名规则 变量名不能加$命名只能使用英文字母、数字和下划线,首个字母不能以数字开头中间不能有空格。可以有下划线不能使用标点符号不能使用bash中的关键字 username"tom"引用 $userna…...
Linux 发行版 Debian 12.1 发布
在今年 6 月初,Debian 12“bookworm”发布,而日前 Debian 迎来了 12.1 版本,主要修复系统用户创建等多个安全问题。 Debian 是最古老的 GNU / Linux 发行版之一,也是许多其他基于 Linux 的操作系统的基础,包括 Ubuntu…...
【Rust】Rust学习 第七章使用包、Crate和模块管理不断增长的项目
目前为止,我们编写的程序都在一个文件的一个模块中。伴随着项目的增长,你可以通过将代码分解为多个模块和多个文件来组织代码。一个包可以包含多个二进制 crate 项和一个可选的 crate 库。伴随着包的增长,你可以将包中的部分代码提取出来&…...
网站SSL安全证书是什么及其重要性
网站SSL安全证书具体来说是一个数字文件,是由受信任的数字证书颁发机构(CA机构)进行审核颁发的,其中包含CA发布的信息,该信息表明该网站已使用加密连接进行了安全保护。 网站SSL安全证书也被称为SSL证书、https证书和…...
Android Alarm闹钟API使用心得
前言 有什么办法可以在不打开App的时候,也能够触发一些操作呢?比如说发送通知,解决这个需求的办法有很多种选择,比如说官方推荐的WorkManager API,可以在后台执行一次性、耗时、定时的任务,但WorkManager是…...
什么是业务敏捷,如何实现业务敏捷?
点击链接了解详情 作者介绍 前言 随着越来越多行业的企业开始关注敏捷,业务敏捷(Business Agility)成为一个新的热点。毕竟大部分的行业和组织与软件无关,但是依然要实现业务上的敏捷,所以这个系列会主要谈两点&#…...
ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639)
安全之安全(security)博客目录导读 ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-7 (CVE-2018-3639) 二、静态缓解(Static mitigation) 三、动态缓解(Dynamic mitigation) 一、ATF(TF-A)安全通告 TFV-7 (CVE-2018…...
第三天课程下
1.项目目录介绍和运行流程 工程化开发模式中:这里不再直接编写模板语法,通过 App.vue 提供结构渲染 main.js文件 // 文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html // 1. 导入 Vue 核心包 import Vue from vue// …...
嵌入式编译FFmpeg6.0版本并且组合x264
下载直通车:我用的是6.0版本的 1.准备编译: 2.进入ffmpeg源码目录,修改Makefile,添加编译选项: CFLAGS -fPIC 不加会报错 3.使用命令直接编译 ./configure --cross-prefix/home/xxx/bin/arm-linux-gnueabihf- --enable-cross-compile --targ…...
原子css 和 组件化css如何搭配使用
如果让你来实现下面这种页面,该怎么实现呢 原子化和css组件化方式写法,可以搭配起来使用,常用的css 原子css 比如 下面这些类似flex 布局,lstn curser-pointer 等常用的或者 具备一定规律性的padding margin 样式可以抽取为单独…...
Python 实现Selenium录屏的一种方法(图片整合成动态图)
由于UI层自动化的不稳定性,经常会遇到执行中断或用例失败的问题,以下是一些常见的措施。 1.详细的日志 2.定位出错时截图 3.Pytest的缓存机制(可以记录成功了哪些失败了哪些) 4.自动重试机制(如pytest-rerunfailures) 5.用例录像 用例录像是最直观的一…...
【设计模式——学习笔记】23种设计模式——策略模式Strategy(原理讲解+应用场景介绍+案例介绍+Java代码实现)
文章目录 案例引入传统方案实现实现分析 介绍基本介绍登场角色 案例实现案例一类图实现 案例二类图实现问答 策略模式在JDK源码中的使用总结文章说明 案例引入 有各种鸭子,比如野鸭、北京鸭、水鸭等。 鸭子有各种行为,比如走路、叫、飞行等。不同鸭子的…...
通讯商二要素Api接口验证真伪
随着互联网的普及和各种社交平台、电商平台、金融平台的发展,许多业务都需要用户进行实名认证,这也就涉及到了手机号码和姓名的验证问题。为了解决这个问题,现在有很多运营商提供的二要素API接口能够进行手机号码和姓名的验证,本文…...
React源码解析18(6)------ 实现useState
摘要 在上一篇文章中,我们已经实现了函数组件。同时可以正常通过render进行渲染。 而通过之前的文章,beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。 实现之前,我们要先修改一下我们的index.js文件&#x…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
